package com.sz.biz.logistics.billing.service.impl;

import com.google.common.collect.Lists;
import com.sz.biz.common.base.dto.AreaDto;
import com.sz.biz.common.base.entity.ExchangeRate;
import com.sz.biz.common.base.service.AreaService;
import com.sz.biz.common.base.service.ExchangeRateService;
import com.sz.biz.common.constants.SystemConstants;
import com.sz.biz.common.constants.TransportConstants;
import com.sz.biz.common.customer.entity.CusCustomer;
import com.sz.biz.common.customer.service.CustomerService;
import com.sz.biz.logistics.base.dto.RegionTypeCode;
import com.sz.biz.logistics.base.dto.WeightSchemaDto;
import com.sz.biz.logistics.base.entity.*;
import com.sz.biz.logistics.base.service.*;
import com.sz.biz.logistics.billing.dto.CalcTypeEnum;
import com.sz.biz.logistics.billing.dto.QuotationCalcObject;
import com.sz.biz.logistics.billing.dto.QuotationCalcResult;
import com.sz.biz.logistics.billing.service.QuotationCalcService;
import com.sz.biz.logistics.constants.ErrorCodes;
import com.sz.biz.logistics.constants.QuotationConstants;
import com.sz.biz.logistics.core.dto.ProductCargotypeRelDto;
import com.sz.biz.logistics.core.dto.ProductDto;
import com.sz.biz.logistics.core.dto.QuotationDto;
import com.sz.biz.logistics.core.entity.*;
import com.sz.biz.logistics.core.service.*;
import com.sz.biz.logistics.ord.entity.*;
import com.sz.biz.logistics.ord.service.OrdOrderAddressService;
import com.sz.biz.logistics.ord.service.OrdOrderService;
import com.sz.biz.logistics.ord.service.OrdOrderServiceAssignService;
import com.sz.biz.logistics.ord.service.OrdSubOrderService;
import com.sz.common.base.dao.ParamData;
import com.sz.common.base.exception.BizException;
import com.sz.common.base.exception.ErrorCode;
import com.sz.common.base.exception.Exceptions;
import com.sz.common.base.utils.*;
import com.sz.common.base.utils.CollectionUtils;
import com.sz.common.base.utils.StringUtils;
import com.sz.common.core.system.entity.SysDict;
import com.sz.common.core.system.service.SysDictService;
import com.sz.common.core.system.service.UnitConversionService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * Function: 计算价格的service. <br>
 * Date: 2017年04月05日 17:52  <br>
 * Author: wentao.chang
 */
@org.springframework.stereotype.Service
public class QuotationCalcServiceImpl implements QuotationCalcService {

    private final String CNY = "CNY";
    private final String CN = "CN";
    private final String MAX = "max";
    private final String KG = "kg";
    private final String NULLUID = "-1";

    @Autowired
    private OrdOrderService orderService;
    @Autowired
    private OrdOrderAddressService orderAddressService;

    @Autowired
    private QuotationService quotationService;

    @Autowired
    private PortService portService;

    @Autowired
    private AirportService airportService;

    @Autowired
    private AreaService areaService;

    @Autowired
    private PriceQuotationStartService quotationStartService;

    @Autowired
    private PriceQuotationItemService quotationItemService;

    @Autowired
    private QuotationSetService quotationSetService;

    @Autowired
    private RegionService regionService;

    @Autowired
    private RegionItemService regionItemService;

    @Autowired
    private WeightSchemaService weightSchemaService;

    @Autowired
    private CustomerService customerService;

    @Autowired
    private UnitConversionService unitConversionService;

    @Autowired
    private ProductService productService;

    @Autowired
    private ProductServiceRelService productServiceRelService;

    @Autowired
    private ServiceService serviceService;

    @Autowired
    private ServiceTypeRelService serviceTypeRelService;

    @Autowired
    private PriceComboService priceComboService;

    @Autowired
    private ExchangeRateService exchangeRateService;

    @Autowired
    private OrdSubOrderService subOrderService;

    @Autowired
    private ProductCargoTypeRelService productCargoTypeRelService;
    @Autowired
    private ProductGroupService productGroupService;
    @Autowired
    private SysDictService sysDictService;

    @Autowired
    private OrdOrderServiceAssignService orderServiceAssignService;


    /**
     * 使用运单号计算销售价
     *
     * @param orderNo   orderNo
     * @param isFromPre 是否是预估费用
     */
    @Override
    public QuotationCalcObject calcOrderSalePrice(String orderNo, boolean isFromPre) {
        OrdOrder order = orderService.findByOrderNo(orderNo);
        OrdOrderAddress orderAddress = orderAddressService.findByOrderNo(orderNo);
        if (order == null) {
            throw new BizException("waybillno is miss....");
        }
        return calcOrderSalePrice(order, orderAddress, isFromPre);
    }

    /**
     * 使用运单计算销售价
     *
     * @param order        order
     * @param orderAddress orderAddress
     */
    @Override
    public QuotationCalcObject calcOrderSalePrice(OrdOrder order, OrdOrderAddress orderAddress, boolean isFromPre) {
        //0.运单号不空获取运单信息
        QuotationCalcObject calcObject = new QuotationCalcObject(order.getOrderNo());
        calcObject.setFromPre(isFromPre);
        //1.服务的分派信息
        OrdServiceAssign serviceAssign = orderServiceAssignService.findOkAssignByOrderNo(order.getOrderNo());
        compositAssignService(calcObject, serviceAssign);
        //2.组装订单的信息
        compositOrderValues(calcObject, order, orderAddress);
        //3.计算销售价
        calcOrderSalePrice(calcObject);
        //4.按协议价<--套餐价<---等级价等级最高计算
        List<QuotationCalcResult> resultFilter = filterMatchResult(calcObject);
        calcObject.setResult(resultFilter);
        return calcObject;
    }

    private void compositAssignService(QuotationCalcObject calcObject, OrdServiceAssign serviceAssign) {
        if (serviceAssign == null) {
            throw new BizException("没有查询到服务分派的结果，请稍后重试");
        }
        if (!serviceAssign.getIsAssignOk()) {
            throw new BizException("服务分派失败，以下为服务分派的信息:\r\n" + serviceAssign.getCalcMsg());
        }

        //只对已分派的服务进行计费
        if (!StringUtils.isEmpty(serviceAssign.getSt001Uid())) {
            calcObject.getAssignedServiceUids().add(serviceAssign.getSt001Uid());
        }
        if (!StringUtils.isEmpty(serviceAssign.getSt002Uid())) {
            calcObject.getAssignedServiceUids().add(serviceAssign.getSt002Uid());
        }
        if (!StringUtils.isEmpty(serviceAssign.getSt003Uid())) {
            calcObject.getAssignedServiceUids().add(serviceAssign.getSt003Uid());
        }
        if (!StringUtils.isEmpty(serviceAssign.getSt004Uid())) {
            calcObject.getAssignedServiceUids().add(serviceAssign.getSt004Uid());
        }
        if (!StringUtils.isEmpty(serviceAssign.getSt005Uid())) {
            calcObject.getAssignedServiceUids().add(serviceAssign.getSt005Uid());
        }
        if (!StringUtils.isEmpty(serviceAssign.getSt006Uid())) {
            calcObject.getAssignedServiceUids().add(serviceAssign.getSt006Uid());
        }
    }

    /**
     * 按协议价<--套餐价<---等级价等级最高计算
     *
     * @param calcObject calcObject
     * @return List
     */
    private List<QuotationCalcResult> filterMatchResult(QuotationCalcObject calcObject) {
        List<QuotationCalcResult> resultFilter = new ArrayList<>();
        //3.1协议价
        calcObject.getResult().forEach(result -> {
            if (result.getQuotationGradeType() == QuotationConstants.GradeType.AGREEMENT) {
                resultFilter.add(result);
            }
        });
        //3.2套餐价
        if (resultFilter.size() == 0) {
            calcObject.getResult().forEach(result -> {
                if (result.getQuotationGradeType() == QuotationConstants.GradeType.SET) {
                    resultFilter.add(result);
                }
            });
        }
        //3.3等级价的取最高等级1<-2<-3<-4<-5
        if (resultFilter.size() == 0) {
            //此map保证只放第一组等级价
            Map<String, String> quotationIdMap = new HashMap<>();
            calcObject.getResult().forEach(result -> {
                if (result.getQuotationGradeType() == QuotationConstants.GradeType.GRADE) {
                    quotationIdMap.put(result.getQuotationId() + "", "--我是没有用的字符串--");
                    if (quotationIdMap.size() == 1) {
                        resultFilter.add(result);
                    }
                }
            });
        }
        return resultFilter;
    }

    /**
     * 使用传入的信息计算销售价,目前只在测试类中使用到了此方法
     */
    @Override
    @Deprecated
    public QuotationCalcObject calcOrderSalePrice(int customerId, String cargoTypeCode,
                                                  BigDecimal weight, BigDecimal volume, String unitCode,
                                                  String productUid,
                                                  String fromCountry, String fromProvince,
                                                  String fromCity, String fromArea, String fromStreet,
                                                  String fromPostcode,
                                                  String toCountry, String toProvince,
                                                  String toCity, String toArea, String toStreet,
                                                  String toPostcode) {
        QuotationCalcObject calcObject = new QuotationCalcObject(customerId, cargoTypeCode,
                weight, volume, unitCode,
                fromCountry, fromProvince, fromCity, fromArea, fromStreet, fromPostcode,
                toCountry, toProvince, toCity, toArea, toStreet, toPostcode);
        calcObject.setFetchCountry(calcObject.getFromCountry());
        calcObject.setFetchProvince(calcObject.getFromProvince());
        calcObject.setFetchCity(calcObject.getFromCity());
        calcObject.setFetchArea(calcObject.getFromArea());
        calcObject.setFetchStreet(calcObject.getFromStreet());
        calcObject.setFetchPostcode(calcObject.getFromPostcode());
        calcObject.setOrderDate(new Date());
        calcObject.setProductUid(productUid);

        calcOrderSalePrice(calcObject);
        return calcObject;
    }


    /**
     * 试算产品的销售价
     *
     * @param customerId    customerId
     * @param groupType     陆运、海运、空运
     * @param productUid    productUid
     * @param calcDate      calcDate
     * @param cargoTypeCode cargoTypeCode
     * @param currencyCode  currencyCode
     * @param weight        weight
     * @param volume        volume
     * @param unitCode      unitCode
     * @param fromCountry   fromCountry
     * @param fromProvince  fromProvince
     * @param fromCity      fromCity
     * @param fromArea      fromArea
     * @param fromStreet    fromStreet
     * @param fromPostcode  fromPostcode
     * @param toCountry     toCountry
     * @param toProvince    toProvince
     * @param toCity        toCity
     * @param toArea        toArea
     * @param toStreet      toStreet
     * @param toPostcode    toPostcode
     * @param domainId      domainId
     * @return QuotationCalcObject
     * @throws ExecutionException   ExecutionException
     * @throws InterruptedException InterruptedException
     */
    @Override
    public QuotationCalcObject calcProductSalePrice(int customerId, List<Integer> groupType, String productUid,
                                                    Date calcDate, String cargoTypeCode, String currencyCode,
                                                    BigDecimal weight, BigDecimal volume, String unitCode, BigDecimal announcePrice,
                                                    String fromCountry, String fromProvince, String fromCity,
                                                    String fromArea, String fromStreet, String fromPostcode,
                                                    String fetchCountry, String fetchProvince, String fetchCity,
                                                    String fetchArea, String fetchStreet, String fetchPostcode,
                                                    String toCountry, String toProvince, String toCity,
                                                    String toArea, String toStreet, String toPostcode,
                                                    int startPort, int endPort, List<String> optionServices,
                                                    boolean isCalcAlloption, boolean isTry,
                                                    int domainId)
            throws ExecutionException, InterruptedException {
        QuotationCalcObject calcObject = new QuotationCalcObject(customerId, cargoTypeCode,
                weight, volume, unitCode,
                fromCountry, fromProvince, fromCity, fromArea, fromStreet, fromPostcode,
                toCountry, toProvince, toCity, toArea, toStreet, toPostcode);
        //揽收地址
        calcObject.setFetchCountry(fetchCountry);
        calcObject.setFetchProvince(fetchProvince);
        calcObject.setFetchCity(fetchCity);
        calcObject.setFetchArea(fetchArea);
        calcObject.setFetchStreet(fetchStreet);
        calcObject.setFetchPostcode(fetchPostcode);
        calcObject.setAnnouncePrice(announcePrice);
        calcObject.setStartPort(startPort);
        calcObject.setEndPort(endPort);
        calcObject.setOptionServices(optionServices);
        calcObject.setCalcAlloption(isCalcAlloption);
        calcObject.setTry(isTry);

        Date realDate = calcDate == null ? new Date() : calcDate;
        calcObject.setOrderDate(realDate);
        //币种
        calcObject.setCustCurrencyCode(currencyCode);

        if (StringUtils.isEmpty(productUid)) {
            //1、获取产品组的Map集合
            List<ProductGroup> pgs = productGroupService.list();
            Map<String, ProductGroup> pgMap = EntityUtils.getStringKeyMapFromBeans(pgs, "id");

            //2、匹配产品在起点、终点范围的所有产品数据
            calcObject.putMsg("无产品匹配报价单");
            List<Product> products = productService.findByRegion(
                    "1-" + fromCountry + "|",
                    getCountryPreCode(fromCountry) + "-" + fromCountry + "-" + calcObject.getFromProvince() + "|",
                    getCountryPreCode(fromCountry) + "-" + fromCountry + "-" + calcObject.getFromCity() + "|",
                    getCountryPreCode(fromCountry) + "-" + fromCountry + "-" + calcObject.getFromArea() + "|",
                    getCountryPreCode(fromCountry) + "-" + fromCountry + "-" + calcObject.getFromStreet() + "|",
                    "4-" + fromCountry + "-" + calcObject.getFromPostcode() + "|",
                    "1-" + toCountry + "|",
                    getCountryPreCode(toCountry) + "-" + toCountry + "-" + calcObject.getToProvince() + "|",
                    getCountryPreCode(toCountry) + "-" + toCountry + "-" + calcObject.getToCity() + "|",
                    getCountryPreCode(toCountry) + "-" + toCountry + "-" + calcObject.getToArea() + "|",
                    getCountryPreCode(toCountry) + "-" + toCountry + "-" + calcObject.getToStreet() + "|",
                    "4-" + toCountry + "-" + calcObject.getToPostcode() + "|"
            );

            if (!CollectionUtils.isEmpty(products)) {

                //默认只能看到公共的产品
                Integer wbFlag = 0;//白黑名单标志 0:可以看到公共产品和自己设置的白名单产品 1: 白名单(只能看白名单里的产品)； 2： 黑名单(不能看的产品)
                ParamData pd = new ParamData();
                if (domainId == SystemConstants.SystemType.CUSTOMER && calcObject.getCustomerId() != 0) {
                    pd = productService.populateCustomerProduct(customerId, pd);
                }

                //创建一个线程池
                ExecutorService pool = Executors.newFixedThreadPool(products.size());
                List<Future<QuotationCalcObject>> results = new ArrayList<>();

                for (Product product : products) {
                    //过滤掉产品组不在客户选择的产品组中的产品
                    if (!CollectionUtils.isEmpty(groupType)) {
                        ProductGroup pg = pgMap.get(product.getProductGroupLeafId().toString());
                        //判断当前产品的产品组是否在要查询的组类别中
                        if (pg == null || !groupType.contains(pg.getParentId())) {
                            calcObject.putMsg("产品，uid：" + product.getUid() +
                                    "code:" + product.getCode() + "name:" + product.getName() + "的产品组不匹配，过滤掉"
                            );
                            continue;
                        }
                    }
                    //只有客户系统才需要处理黑白名单的问题，运营系统不需要处理
                    if (domainId == SystemConstants.SystemType.CUSTOMER) {
                        //产品可见性
                        List<Integer> productGroupIds = Lists.newArrayList();
                        List<String> productUids = new ArrayList<>();
                        if (calcObject.getCustomerId() != 0) {
                            //白名单的产品组和产品的uid,如果
                            productGroupIds = (List<Integer>) pd.get("productGroupIds");
                            productUids = (List<String>) pd.get("productUids");
                            wbFlag = (Integer) pd.get("wbFlag");
                        }
                        //客户的产品可见性
                        //如果产品的产品组在返回的白名单中或者在产品的白名单中，那么就继续计算
                        if (productGroupIds.contains(product.getProductGroupLeafId()) || productUids.contains(product.getUid())) {
                            calcObject.putMsg("产品，uid：" + product.getUid() +
                                    "code:" + product.getCode() + "name:" + product.getName() + "在白名单内，继续计算"
                            );
                        } else {
                            //公共的产品，当wbFlag=0时只能看到公共的产品，这时如果当前产品为私有的，那么直接过滤掉
                            if (wbFlag == 0 && !product.getIsPublic()) {
                                calcObject.putMsg("产品，uid：" + product.getUid() +
                                        "code:" + product.getCode() + "name:" + product.getName() + "不在白名单且非公共产品，过滤掉"
                                );
                                continue;
                            }
                            //只能看分配的产品和私有产品
                            //当wbFlag=1时只能看到分给它的产品组的产品，其它的都不应看到
                            if (wbFlag == 1) {
                                calcObject.putMsg("产品，uid：" + product.getUid() +
                                        "code:" + product.getCode() +
                                        "name:" + product.getName() +
                                        "不在白名单，过滤掉"
                                );
                                continue;
                            }
                        }
                    }

                    calcObject.setGroupId(product.getProductGroupLeafId());

                    calcObject.putMsg("匹配到产品，uid：" + product.getUid() + "code:" + product.getCode() + "name:" + product.getName());

                    Callable c1 = new SaleCalcCallable(product, calcObject);
                    //执行任务并获取Future对象
                    results.add(pool.submit(c1));
                }
                for (Future<QuotationCalcObject> futrue : results) {
                    QuotationCalcObject calcObjectGet = futrue.get();
                    calcObject.putMsgs(calcObjectGet.getCalcMsgs());
                    calcObject.getResult().addAll(calcObjectGet.getResult());
                    calcObject.setComboCode(calcObjectGet.getComboCode());
                }
                //关闭线程池
                pool.shutdown();
            }
        } else {
            calcObject.setProductUid(productUid);
            calcOrderSalePrice(calcObject);
        }
        for (int index = calcObject.getResult().size() - 1; index >= 0; index--)

        {
            QuotationCalcResult resut = calcObject.getResult().get(index);
            if (!resut.isHasResult()) {
                calcObject.getResult().remove(index);
            }
        }
        calcObject.getResult().

                sort(Comparator.comparing(o -> (o.getProductName() + o.getQuotationName())));
        return calcObject;
    }

    private String getCountryPreCode(String countryCode) {
        return CN.equals(countryCode) ? "2" : "3";
    }

    /**
     * 计算订单的成本价，暂支持单供应商
     *
     * @param orderNo orderNo
     */
    @Override
    public QuotationCalcObject calcOrderCostPrice(String orderNo) {
        OrdOrder order = orderService.findByOrderNo(orderNo);
        OrdOrderAddress orderAddress = orderAddressService.findByOrderNo(orderNo);
        if (order == null) {
            throw new BizException("waybillno is miss....");
        }
        return calcOrderCostPrice(order, orderAddress);
    }

    /**
     * 计算运单的成本价，暂支持单供应商
     *
     * @param order        order
     * @param orderAddress orderAddress
     */
    @Override
    public QuotationCalcObject calcOrderCostPrice(OrdOrder order, OrdOrderAddress orderAddress) {
        QuotationCalcObject calcObject = new QuotationCalcObject(order.getOrderNo());
        compositOrderValues(calcObject, order, orderAddress);
        //1.服务的分派信息
        OrdServiceAssign serviceAssign = orderServiceAssignService.findOkAssignByOrderNo(order.getOrderNo());
        compositAssignService(calcObject, serviceAssign);
        calcOrderCostPrice(calcObject);
        return calcObject;
    }

    private void compositOrderValues(QuotationCalcObject calcObject, OrdOrder order, OrdOrderAddress orderAddress) {
        //客户id
        calcObject.setCustomerId(order.getCustomerId());
        calcObject.putMsg("获取运单信息：id" + order.getId()
                + ",运单号:" + order.getOrderNo() + ",下单时间:" + DateUtils.formatSimpleDate(order.getCreateTime()));
        if (order.getOrderStatus().getCode().compareTo("104") < 0 && !calcObject.isFromPre()) {
            throw new BizException(-1, "运单还未揽收不能计费");
        }

        //产品id
        calcObject.setProductUid(order.getProductUid());

        //始发地
        calcObject.setFromCountry(orderAddress.getFromCountry());
        calcObject.setFromProvince(orderAddress.getFromProvince());
        calcObject.setFromCity(orderAddress.getFromCity());
        calcObject.setFromArea(orderAddress.getFromDistrict());
        calcObject.setFromStreet(orderAddress.getFromStreet());

        //目的地
        calcObject.setToCountry(orderAddress.getToCountry());
        calcObject.setToProvince(orderAddress.getToProvince());
        calcObject.setToCity(orderAddress.getToCity());
        calcObject.setToArea(orderAddress.getToDistrict());
        calcObject.setToStreet(orderAddress.getToStreet());

        //揽收地址
        calcObject.setFetchCountry(orderAddress.getFetchCountry());
        calcObject.setFetchProvince(orderAddress.getFetchProvince());
        calcObject.setFetchCity(orderAddress.getFetchCity());
        calcObject.setFetchArea(orderAddress.getFetchDistrict());
        calcObject.setFetchStreet(orderAddress.getFetchStreet());
        calcObject.setFetchPostcode(orderAddress.getFetchPostcode());

        //货物类型
        calcObject.setCargoTypeCode(order.getCargoType());

        //产品uid
        calcObject.setProductUid(order.getProductUid());
        //揽收时间
        //重量  预估费用
        if (calcObject.isFromPre()) {
            calcObject.setWeight(order.getCusWeight());
            calcObject.setWeightUnit(order.getCusWeightUnit());
            calcObject.setOrderDate(order.getLastUpdateTime());
        } else {
            calcObject.setWeight(order.getSupWeight());
            calcObject.setWeightUnit(order.getSupWeightUnit());
            calcObject.setOrderDate(order.getReceiveCargoTime());
        }
        List<OrdSubOrder> subOrders = subOrderService.findByOrderNos(Collections.singletonList(order.getOrderNo()));
        //计算体积
        BigDecimal volume = BigDecimal.ZERO;
        String prefix = "计算产品体积重时(subOrder.lwhUnit):";
        for (OrdSubOrder subOrder : subOrders) {
            if (subOrder.getWidth() != null && subOrder.getHeight() != null && subOrder.getLength() != null) {
                try {
                    volume = getSubOrderVolume(volume, subOrder);
                } catch (BizException e) {
                    throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMsg());
                } catch (Exception e) {
                    throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMessage());
                }
            }
        }
        calcObject.setVolume(volume);
    }

    /**
     * 使用传入的信息计算成本价
     */
    @Override
    public QuotationCalcObject calcCostPrice(String cargoTypeCode,
                                             BigDecimal weight, BigDecimal volume, String unitCode,
                                             String productUid,
                                             String fromCountry, String fromProvince,
                                             String fromCity, String fromArea, String fromStreet,
                                             String fromPostcode,
                                             String toCountry, String toProvince,
                                             String toCity, String toArea, String toStreet,
                                             String toPostcode) {
        QuotationCalcObject calcObject = new QuotationCalcObject(cargoTypeCode,
                weight, volume, unitCode,
                fromCountry, fromProvince, fromCity, fromArea, fromStreet, fromPostcode,
                toCountry, toProvince, toCity, toArea, toStreet, toPostcode);

        calcObject.setFetchCountry(calcObject.getFromCountry());
        calcObject.setFetchProvince(calcObject.getFromProvince());
        calcObject.setFetchCity(calcObject.getFromCity());
        calcObject.setFetchArea(calcObject.getFromArea());
        calcObject.setFetchStreet(calcObject.getFromStreet());
        calcObject.setFetchPostcode(calcObject.getFromPostcode());

        calcObject.setOrderDate(new Date());
        calcObject.setProductUid(productUid);
        //1.检查基本信息
        checkBaseCalcInfo(calcObject);
        //2.获取产品下面挂的服务
        List<ProductServiceRel> services =
                productService.findServicesByUidAndDate(calcObject.getProductUid(), calcObject.getOrderDate());
        calcObject.setServices(services);
        for (ProductServiceRel serviceRel : calcObject.getServices()) {
            //3.纯粹的计算过程
            calcCostResult(calcObject, serviceRel.getServiceUid());
        }
        return calcObject;
    }

    /**
     * 计算单个服务的成本 支持无服务试算
     *
     * @param cargoTypeCode cargoTypeCode
     * @param weight        weight
     * @param volume        volume
     * @param fromCountry   fromCountry
     * @param fromProvince  fromProvince
     * @param fromCity      fromCity
     * @param fromStreet    fromStreet
     * @param fromPostcode  fromPostcode
     * @param toCountry     toCountry
     * @param toProvince    toProvince
     * @param toCity        toCity
     * @param toStreet      toStreet
     * @param toPostcode    toPostcode
     */
    @Override
    public QuotationCalcObject calcServiceCostPrice(String serviceUid, Date date, int settleMentId,
                                                    String cargoTypeCode,
                                                    BigDecimal weight, BigDecimal volume, String unitCode,
                                                    String fromCountry, String fromProvince, String fromCity,
                                                    String fromArea, String fromStreet, String fromPostcode,
                                                    String toCountry, String toProvince, String toCity,
                                                    String toArea, String toStreet, String toPostcode)
            throws ExecutionException, InterruptedException {
        QuotationCalcObject calcObject = new QuotationCalcObject(cargoTypeCode, weight, volume, unitCode,
                fromCountry, fromProvince, fromCity, fromArea, fromStreet, fromPostcode,
                toCountry, toProvince, toCity, toArea, toStreet, toPostcode);
        //揽收地址
        calcObject.setFetchCountry(calcObject.getFromCountry());
        calcObject.setFetchProvince(calcObject.getFromProvince());
        calcObject.setFetchCity(calcObject.getFromCity());
        calcObject.setFetchArea(calcObject.getFromArea());
        calcObject.setFetchStreet(calcObject.getFromStreet());
        calcObject.setFetchPostcode(calcObject.getFromPostcode());

        calcObject.setSettlementId(settleMentId);
        Date realDate = date == null ? new Date() : date;
        calcObject.setOrderDate(realDate);

        if (StringUtils.isEmpty(serviceUid)) {
            List<String> orderedMsg = new ArrayList<>();
            orderedMsg.add("无服务匹配报价单");
            List<QuotationStart> starts = quotationStartService.findAllEnableStarts(1, calcObject.getOrderDate());
            //匹配起点
            List<Integer> quotationIds = new ArrayList<>();
            for (QuotationStart qs : starts) {
                //国家
                if (qs.getType() == 1 && qs.getCountry().equals(calcObject.getFromCountry())) {
                    quotationIds.add(qs.getQuotationId());
                    continue;
                }
                //国内地区
                if (qs.getType() == 2 && CN.equals(calcObject.getFromCountry())) {
                    if (qs.getAreaId().equals(calcObject.getFromProvince())) {
                        quotationIds.add(qs.getQuotationId());
                        continue;
                    }
                    if (qs.getAreaId().equals(calcObject.getFromCity())) {
                        quotationIds.add(qs.getQuotationId());
                        continue;
                    }
                    if (qs.getAreaId().equals(calcObject.getFromArea())) {
                        quotationIds.add(qs.getQuotationId());
                        continue;
                    }
                    if (qs.getAreaId().equals(calcObject.getFromStreet())) {
                        quotationIds.add(qs.getQuotationId());
                        continue;
                    }
                }
                //国外地区
                if (qs.getType() == 3 &&
                        qs.getCountry().equals(calcObject.getFromCountry()) &&
                        qs.getAreaId().equals(calcObject.getFromCity())) {
                    quotationIds.add(qs.getQuotationId());
                }
            }
            //查找起点为空的所有报价单
            List<Quotation> noneStartOnlineQuotations = quotationService.findNoneStartOnlineQuotations(1, calcObject.getOrderDate());
            //匹配起点
            List<Integer> noneStartQuotationIds = new ArrayList<>();
            if (!CollectionUtils.isEmpty(noneStartOnlineQuotations)) {
                noneStartOnlineQuotations.forEach(item -> noneStartQuotationIds.add(item.getId()));
            }
            //多线程计算
            Map<String, Quotation> repeatServiceUidMap = new HashMap<>();
            List<Quotation> quotations = new ArrayList<>();
            if (!CollectionUtils.isEmpty(quotationIds)) {
                quotations = quotationService.findByIds(quotationIds);
                quotations.forEach(item -> repeatServiceUidMap.put(item.getBizUid(), item));
                orderedMsg.add("匹配到下列起点非空的报价单：");
                getQuotationMsg(quotations, calcObject);
                orderedMsg.addAll(calcObject.getCalcMsgs());
                calcObject.getCalcMsgs().clear();
            }
            if (!CollectionUtils.isEmpty(noneStartQuotationIds)) {
                List<Quotation> noneStartQuotations = quotationService.findByIds(noneStartQuotationIds);
                //起点空的放在最后面进行计算
                quotations.addAll(noneStartQuotations);
                orderedMsg.add("匹配到下列起点空的报价单：");
                getQuotationMsg(noneStartQuotations, calcObject);
                orderedMsg.addAll(calcObject.getCalcMsgs());
                calcObject.getCalcMsgs().clear();
            }

            if (CollectionUtils.isEmpty(quotations)) {
                throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_NOMATCHED_START, calcObject, "");
            }
            if (!CollectionUtils.isEmpty(quotations)) {
                //创建一个线程池
                ExecutorService pool = Executors.newFixedThreadPool(quotations.size());
                List<Future<QuotationCalcObject>> results = new ArrayList<>();
                for (Quotation quotation : quotations) {
                    if (repeatServiceUidMap.get(quotation.getBizUid()) != null &&
                            noneStartQuotationIds.contains(quotation.getId())) {
                        orderedMsg.add("---过滤掉有起点的服务的报价code：" + quotation.getCode());
                        continue;
                    }
                    if (!quotation.getCargoTypeCode().equals(calcObject.getCargoTypeCode())) {
                        orderedMsg.add("---过滤掉货物类型不匹配的报价code：" + quotation.getCode());
                        continue;
                    }
                    boolean notRmbMonth = !CNY.equals(quotation.getCurrencyCode()) ||
                            !(quotation.getSettlementTypeId() != null && quotation.getSettlementTypeId() == 1);
                    if (notRmbMonth) {
                        orderedMsg.add("---过滤掉非月结且非人民币的报价code：" + quotation.getCode());
                        continue;
                    }
                    Callable c1 = new CostCalcCallable(quotation, calcObject);
                    //执行任务并获取Future对象
                    results.add(pool.submit(c1));
                }

                calcObject.getCalcMsgs().addAll(orderedMsg);
                for (Future<QuotationCalcObject> futrue : results) {
                    QuotationCalcObject calcObjectGet = futrue.get();
                    calcObject.putMsgs(calcObjectGet.getCalcMsgs());
                    calcObjectGet.getResult().stream()
                            .filter(QuotationCalcResult::isHasResult)
                            .forEach(calcResult -> calcObject.getResult().add(calcResult));
                }
                calcObject.getResult().sort(Comparator.comparing(QuotationCalcResult::getAmount));
                //关闭线程池
                pool.shutdown();
            }

        } else {
            calcObject.setCalcWeight(calcObject.getWeight());
            calcObject.setCalcWeightUnit(calcObject.getWeightUnit());
            //1.检查基本信息
            checkBaseCalcInfo(calcObject);
            //2.纯粹的服务计算过程
            calcCostResult(calcObject, serviceUid);
        }

        return calcObject;
    }


    private class CostCalcCallable implements Callable {
        private Quotation quotation;
        private QuotationCalcObject calcObject;

        CostCalcCallable(Quotation quotation, QuotationCalcObject calcObject) {
            this.quotation = quotation;
            byte[] bts = SerializeUtil.serialize(calcObject);
            this.calcObject = (QuotationCalcObject) SerializeUtil.unserialize(bts);
            calcObject.getCalcMsgs().clear();
        }

        @Override
        public QuotationCalcObject call() throws Exception {
            calcObject.putMsg("========子线程计算报价单，报价单uid：" + quotation.getUid() + ",name:" + quotation.getName());
            //已有起点的逻辑，忽略
            calcObject.setIgnoreStarts(true);
            calcObject.setCalcWeightUnit(calcObject.getWeightUnit());
            calcObject.setCalcWeight(calcObject.getWeight());
            try {
                Service service = serviceService.findActivedByUid(quotation.getBizUid());
                calcCostResult(calcObject, quotation, service);
            } catch (BizException e) {
                calcObject.putMsg("=========计算失败,原因代码:" + e.getErrorCode() + ",错误信息：" + e.getMsg());
            }
            return calcObject;
        }
    }

    private class SaleCalcCallable implements Callable {
        private Product product;
        private QuotationCalcObject calcObject;

        SaleCalcCallable(Product product, QuotationCalcObject calcObject) {
            this.product = product;
            byte[] bts = SerializeUtil.serialize(calcObject);
            this.calcObject = (QuotationCalcObject) SerializeUtil.unserialize(bts);
            calcObject.getCalcMsgs().clear();
        }

        @Override
        public QuotationCalcObject call() throws Exception {
            calcObject.putMsg("============子线程计算产品售价，产品uid：" + product.getUid() + ",name:" + product.getName());
            try {
                //已有起点的逻辑，忽略
                calcObject.setIgnoreStarts(true);
                calcObject.setProductUid(product.getUid());
                calcOrderSalePrice(calcObject);
            } catch (BizException e) {
                calcObject.putMsg("============计算失败,原因代码:" + e.getErrorCode() + ",错误信息：" + e.getMsg());
            }
            return calcObject;
        }

    }

    private void calcOrderSalePrice(QuotationCalcObject calcObject) {
        checkAndCompositOrderInfo(calcObject);
        //4.纯粹的计算过程
        String dateStr = DateUtils.formatDetailDate(calcObject.getOrderDate());
        String bizUid = calcObject.getProductUid();
        //获取bizUid上的销售报价
        //  filter by: isDeleted = 0 and isActive = 1 and isOnline = 1 and startEffectTime < orderDate <= endEffectTime
        //  order  by: grade asc ,settlementTypeId desc
        calcObject.putMsg("按bizuid" + bizUid + "以及时间" + dateStr + "匹配销售价");
        //对于一个产品来说会有多个报价单，例如：1级、2级、3级
        List<Quotation> quotations = quotationService.findByBizUidAndTime(bizUid, calcObject.getOrderDate());
        calcOrderQuotations(quotations, calcObject);
    }

    private void calcOrderCostPrice(QuotationCalcObject calcObject) {
        checkAndCompositOrderInfo(calcObject);
        //计算产品的服务
        Collection<ProductServiceRel> productServiceRels = calcObject.getProductServiceRelMap().values();
        for (ProductServiceRel productServiceRel : productServiceRels) {
            if (!calcObject.getAssignedServiceUids().contains(productServiceRel.getServiceUid())) {
                calcObject.putMsg("没有分派服务" + productServiceRel.getServiceUid() + ",忽略其成本价的计算");
                continue;
            }
            calcCostResult(calcObject, productServiceRel.getServiceUid());
        }

    }

    private void checkAndCompositOrderInfo(QuotationCalcObject calcObject) {
        //1.检查基本信息
        checkBaseCalcInfo(calcObject);
        //2.检查业务计算要素
        checkSaleCalcInfo(calcObject);
        //3.获取客户、产品的信息
        compositSaleBizInfo(calcObject);
        //4.组装运单的可选服务
        /*if (!StringUtils.isEmpty(calcObject.getOrderNo())) {
            List<String> optionalServiceUIds = orderAdditionalService.findServiceUidByOrderNo(calcObject.getOrderNo());
            calcObject.getAddtionals().addAll(optionalServiceUIds);
            calcObject.setCheckAddition(true);
        }*/
    }


    /**
     * 组合客户、产品具体的信息
     *
     * @param calcObject calcObject
     */
    private void compositSaleBizInfo(QuotationCalcObject calcObject) {
        //获取客户的信息
        if (calcObject.getCustomerId() != 0) {
            CusCustomer cusCustomer = customerService.findSimpleById(calcObject.getCustomerId());
            calcObject.putMsg("获取客户信息:" + cusCustomer.getUserName());

            /*客户的交易币种 */
            calcObject.setCustCurrencyCode(cusCustomer.getTradingCurrency());
            calcObject.putMsg("从客户信息中获取客户交易币种:" + cusCustomer.getTradingCurrency());
            /*客户的结算方式*/
            calcObject.setSettlementId(cusCustomer.getRefSettlementId());
            calcObject.putMsg("从客户信息中获取客户结算方式:" + cusCustomer.getRefSettlementId());
            calcObject.setComboCode(cusCustomer.getRefCombos());
            calcObject.putMsg("从客户信息中获取客户套餐编码:" + cusCustomer.getRefCombos());
        } else {
            calcObject.putMsg("---传入客户信息为空");
        }

        //获取产品的实体
        if (!StringUtils.isEmpty(calcObject.getProductUid())) {
            ProductDto product = productService.findByUidAndTime(calcObject.getProductUid(), new Date());
            if (product == null) {
                throw new BizException(-1, "按传入时间查询产品信息为空!");
            }
            calcObject.setGroupId(product.getProductGroupLeafId());
            //无运单号的场景
            if (StringUtils.isEmpty(calcObject.getFromCountry())) {
                ProductGroup pg = productGroupService.findById(product.getProductGroupLeafId());
                //海运
                if (pg.getParentId() == TransportConstants.Type.SEA) {
                    Port port = portService.findShortById(calcObject.getStartPort());
                    calcObject.setFromCountry(port.getCountryId());
                    calcObject.setFromCity(port.getCityId());
                    if (CN.equals(port.getCountryId())) {
                        AreaDto ad = areaService.findByAreaId(port.getCityId(), port.getCountryId());
                        calcObject.setFromProvince(ad.getParentId());
                    }
                    port = portService.findShortById(calcObject.getEndPort());
                    calcObject.setToCountry(port.getCountryId());
                    calcObject.setToCity(port.getCityId());
                    if (CN.equals(port.getCountryId())) {
                        AreaDto ad = areaService.findByAreaId(port.getCityId(), port.getCountryId());
                        calcObject.setToProvince(ad.getParentId());
                    }
                }
                //空运
                if (pg.getParentId() == TransportConstants.Type.AIR) {
                    Airport port = airportService.findShortById(calcObject.getStartPort());
                    calcObject.setFromCountry(port.getCountryId());
                    calcObject.setFromCity(port.getCityId());
                    if (CN.equals(port.getCountryId())) {
                        AreaDto ad = areaService.findByAreaId(port.getCityId(), port.getCountryId());
                        calcObject.setFromProvince(ad.getParentId());
                    }
                    port = airportService.findShortById(calcObject.getEndPort());
                    calcObject.setToCountry(port.getCountryId());
                    calcObject.setToCity(port.getCityId());
                    if (CN.equals(port.getCountryId())) {
                        AreaDto ad = areaService.findByAreaId(port.getCityId(), port.getCountryId());
                        calcObject.setToProvince(ad.getParentId());
                    }
                }
            }
            calcObject.setProductId(product.getId());
            calcObject.setProductName(product.getName());
            calcObject.setProductEstimatedTime(product.getEstimatedTime());
            calcObject.setProductEstimatedUnit(product.getEstimatedUnit());
            calcObject.putMsg("获取到产品:name-" + product.getName() + ",uid-" + calcObject.getProductUid());
            compositProductWeight(calcObject, product);
            calcObject.putMsg("换算产品设置的体积系数，最终重量：" +
                    calcObject.getCalcWeight().doubleValue() +
                    calcObject.getCalcWeightUnit());
            //3.获取产品的所有服务
            List<ProductServiceRel> serviceRels = productServiceRelService.findByProductId(product.getId());
            for (ProductServiceRel serviceRel : serviceRels) {
                calcObject.getProductServiceRelMap().put(serviceRel.getServiceUid(), serviceRel);
            }
        } else {
            calcObject.putMsg("---传入产品信息为空！！！！");
        }
    }

    private void compositProductWeight(QuotationCalcObject calcObject, Product product) {
        if (product.getIsVolume() &&
                calcObject.getVolume() != null &&
                product.getVolumeFactor().compareTo(BigDecimal.ZERO) > 0) {
            calcObject.setCalcWeightUnit(KG);
            BigDecimal targetWeight = BigDecimal.ZERO;
            String prefix = "计算产品体积重时(calcObject.weightUnit):";
            try {
                targetWeight = unitConversionService.getTargetWeight(
                        calcObject.getWeight(), calcObject.getWeightUnit(), KG);
            } catch (BizException e) {
                throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMsg());
            } catch (Exception e) {
                throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMessage());
            }
            BigDecimal volumeWeight = calcObject.getVolume().divide(product.getVolumeFactor(), 3, RoundingMode.HALF_UP);
            if (MAX.equals(product.getWeightValueTye())) {
                calcObject.setCalcWeight(volumeWeight.compareTo(targetWeight) > 0 ? volumeWeight : targetWeight);
            } else {
                calcObject.setCalcWeight(volumeWeight.compareTo(targetWeight) > 0 ? targetWeight : volumeWeight);
            }

        } else {
            calcObject.setCalcWeight(calcObject.getWeight());
            calcObject.setCalcWeightUnit(calcObject.getWeightUnit());
        }

        if (product.getWeightLimitMin() != null) {
            BigDecimal targetWeightMin = BigDecimal.ZERO;
            String prefix = "计算产品限重时(weightLimitUnitCode):";
            try {
                targetWeightMin = unitConversionService.getTargetWeight(
                        product.getWeightLimitMin(), product.getWeightLimitUnitCode(), KG);
            } catch (BizException e) {
                throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMsg());
            } catch (Exception e) {
                throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMessage());
            }
            if (targetWeightMin.compareTo(calcObject.getCalcWeight()) > 0) {
                throw new BizException(-1, "货物重量小于产品设置的最低限重");
            }
        }
        if (product.getWeightLimitMax() != null) {
            BigDecimal targetWeightMax = BigDecimal.ZERO;
            String prefix = "计算产品限重时(weightLimitUnitCode):";
            try {
                targetWeightMax = unitConversionService.getTargetWeight(
                        product.getWeightLimitMax(), product.getWeightLimitUnitCode(), KG);
            } catch (BizException e) {
                throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMsg());
            } catch (Exception e) {
                throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMessage());
            }
            if (targetWeightMax.compareTo(calcObject.getCalcWeight()) < 0) {
                throw new BizException(-1, "货物重量大于产品设置的最高限重");
            }
        }
        //检查产品的货物类型
        List<ProductCargotypeRelDto> cargodtos = productCargoTypeRelService.findByProductId(product.getId());
        boolean isHasCargo = false;
        String cargos = "";
        for (ProductCargotypeRelDto cargodto : cargodtos) {
            if (cargodto.getCargoTypeCode().equals(calcObject.getCargoTypeCode())) {
                isHasCargo = true;
            }
            cargos += cargodto.getCargoTypeCode();
        }
        if (!isHasCargo) {
            throw new BizException(-1, "此产品的货物类型" + cargos + "不包含此货物类型" + calcObject.getCargoTypeCode());
        }
        //检查产品的终点范围

    }

    /**
     * 检查最基本的计算条件：起运地，产品，客户，重量等
     *
     * @param calcObject calcObject
     */
    private void checkBaseCalcInfo(QuotationCalcObject calcObject) {
        //1.起运地
        if (StringUtils.isEmpty(calcObject.getFromCountry()) &&
                StringUtils.isEmpty(calcObject.getFromProvince()) &&
                StringUtils.isEmpty(calcObject.getFromCity()) &&
                StringUtils.isEmpty(calcObject.getFromArea()) &&
                StringUtils.isEmpty(calcObject.getFromStreet()) &&
                StringUtils.isEmpty(calcObject.getFromPostcode()) &&
                calcObject.getStartPort() == 0) {
            throwCalcException(ErrorCodes.ERROR_ARGUMENT_ILLEGAL, calcObject, "from address");
        }
        //2.目的地
        if (StringUtils.isEmpty(calcObject.getToCountry()) &&
                StringUtils.isEmpty(calcObject.getToProvince()) &&
                StringUtils.isEmpty(calcObject.getToCity()) &&
                StringUtils.isEmpty(calcObject.getToArea()) &&
                StringUtils.isEmpty(calcObject.getToStreet()) &&
                StringUtils.isEmpty(calcObject.getToPostcode()) &&
                calcObject.getEndPort() == 0) {
            throwCalcException(ErrorCodes.ERROR_ARGUMENT_ILLEGAL, calcObject, "to address");
        }

        //3.必须传递货物类型
        if (StringUtils.isEmpty(calcObject.getCargoTypeCode())) {
            throwCalcException(ErrorCodes.ERROR_ARGUMENT_EMPTY, calcObject, "cargoTypeCode");
        }
        //4.必须传递重量
        if (calcObject.getWeight() == null || calcObject.getWeight().signum() < 0) {
            throwCalcException(ErrorCodes.ERROR_ARGUMENT_ILLEGAL, calcObject, "weight");
        }
        //5.必须传递体积
        if (calcObject.getVolume() == null || calcObject.getVolume().signum() < 0) {
            throwCalcException(ErrorCodes.ERROR_ARGUMENT_ILLEGAL, calcObject, "volume");
        }
        //6.必须传递订单时间 - 收货时间
        if (calcObject.getOrderDate() == null) {
            throwCalcException(ErrorCodes.ERROR_ARGUMENT_ILLEGAL, calcObject, "orderDate");
        }
    }

    /**
     * 检查销售价业务计算需要的信息
     */
    private void checkSaleCalcInfo(QuotationCalcObject calcObject) {
        //必须传递产品uid
        if (StringUtils.isEmpty(calcObject.getProductUid())) {
            throwCalcException(ErrorCodes.ERROR_ARGUMENT_EMPTY, calcObject, "productUid");
        }
    }

    /**
     * 计算价格的销售价
     *
     * @param quotations 待匹配的价格
     * @param calcObject 计算对象
     */
    private void calcOrderQuotations(List<Quotation> quotations, QuotationCalcObject calcObject) {
        if (StringUtils.isEmpty(calcObject.getOrderNo())) {
            OrdServiceAssign assign = orderServiceAssignService.calcForTry(calcObject);
            compositAssignService(calcObject, assign);
        }
        // for 产品销售试算 |正常计算运单的销售和采购客户ID在前面也已经检查 在这里不可能为空
        if (calcObject.getCustomerId() == 0) {
            calcObject.putMsg("--->将使用下面的报价单进行无客户试算:( 销售价 )");
            //getQuotationMsg(quotations, calcObject);
            Quotation quotationMatch = filterSaleQuotation(quotations, calcObject);
            try {
                //报价单集合
                List<QuotationSet> sets = quotationSetService.findByQuotationId(quotationMatch.getId());
                for (QuotationSet serviceSet : sets) {

                    //检查服务分派
                    if (!calcObject.getAssignedServiceUids().contains(serviceSet.getServiceUid())) {
                        calcObject.putMsg("服务分派不包含" + serviceSet.getServiceUid() + ",忽略掉");
                        continue;
                    }
                    calcObject.putMsg("服务分派包含" + serviceSet.getServiceUid() + ",继续进行此服务的计费");

                    //匹配目的分区
                    Service service = serviceService.findActivedByUid(serviceSet.getServiceUid());
                    List<ServiceTypeRel> types = serviceTypeRelService.findByRefId(service.getId());

                    ServiceTypeRel stype = types.get(0);
                    calcObject.putMsg("匹配服务类型" + stype.getServiceTypeCode() + "分区,条件:regionSchemaUid:" + serviceSet.getRegionSchemaUid() +
                            ",date:" + DateUtils.formatDetailDate(calcObject.getOrderDate()));
                    int regionId = matchRegion(stype.getServiceTypeCode(), serviceSet.getRegionSchemaUid(), calcObject, quotationMatch.getId());
                    Region regionMatch = null;
                    if (regionId != -1) {
                        regionMatch = regionService.findById(regionId);
                        calcObject.putMsg("匹配到目标分区uid:" + regionMatch.getUid());
                    }
                    calcSaleResult(calcObject, regionMatch, quotationMatch, serviceSet);
                }
            } catch (Exception e) {
                calcObject.putMsg(e.getMessage());
            }

        } else {
            Quotation quotationMatch = filterSaleQuotation(quotations, calcObject);

            calcObject.putMsg("--->将使用下面的报价单进行计费:(销售价)");
            getQuotationMsg(Collections.singletonList(quotationMatch), calcObject);
            calcObject.setCurrencyCode(quotationMatch.getCurrencyCode());


            List<QuotationSet> sets = quotationSetService.findByQuotationId(quotationMatch.getId());

            for (QuotationSet serviceSet : sets) {

                //检查服务分派
                if (!calcObject.getAssignedServiceUids().contains(serviceSet.getServiceUid())) {
                    calcObject.putMsg("服务分派不包含" + serviceSet.getServiceUid() + ",忽略掉");
                    continue;
                }
                calcObject.putMsg("服务分派包含" + serviceSet.getServiceUid() + ",继续进行此服务的计费");

                calcObject.putMsg("匹配目的地分区,条件:regionSchemaUid:" + serviceSet.getRegionSchemaUid() +
                        ",date:" + DateUtils.formatDetailDate(calcObject.getOrderDate()));
                //匹配目的分区
                Service service = serviceService.findActivedByUid(serviceSet.getServiceUid());
                List<ServiceTypeRel> types = serviceTypeRelService.findByRefId(service.getId());

                ServiceTypeRel stype = types.get(0);
                int regionId = matchRegion(stype.getServiceTypeCode(), serviceSet.getRegionSchemaUid(), calcObject, quotationMatch.getId());
                Region regionMatch = null;
                if (regionId != -1) {
                    regionMatch = regionService.findById(regionId);
                    calcObject.putMsg("匹配到目标分区uid:" + regionMatch.getUid());
                }
                calcSaleResult(calcObject, regionMatch, quotationMatch, serviceSet);
            }
            if (!StringUtils.isEmpty(calcObject.getOrderNo())) {
                if (calcObject.getCustCurrencyCode().equals(calcObject.getCurrencyCode())) {
                    calcObject.setExchangeRateSetting(true);
                    calcObject.setExchangeRate(BigDecimal.ONE);
                    calcObject.getResult().stream().filter(QuotationCalcResult::isHasResult).forEach(result -> {
                        result.setCustCurrencyCode(result.getCurrencyCode());
                        result.setCustCurrencyAmount(result.getAmount());
                        result.setExchangeRate(BigDecimal.ONE);
                    });
                } else {
                    //客户id不为空 就会有客户的货币
                    ExchangeRate exchangeRate = exchangeRateService.findCurrencyRoeByDate(calcObject.getCurrencyCode(),
                            calcObject.getCustCurrencyCode(), calcObject.getOrderDate());
                    if (exchangeRate != null) {
                        calcObject.setExchangeRateSetting(true);
                        calcObject.setExchangeRate(exchangeRate.getRate());
                        calcObject.getResult().stream().filter(QuotationCalcResult::isHasResult).forEach(result -> {
                            result.setCustCurrencyCode(calcObject.getCustCurrencyCode());
                            result.setCustCurrencyAmount(result.getAmount().multiply(exchangeRate.getRate()));
                            result.setExchangeRate(exchangeRate.getRate());
                        });
                    } else {
                        calcObject.getResult().stream().filter(QuotationCalcResult::isHasResult).forEach(result -> {
                            result.setCustCurrencyCode(calcObject.getCustCurrencyCode());
                            result.setCustCurrencyAmount(null);
                            result.setExchangeRate(null);
                        });
                        calcObject.setExchangeRate(null);
                        calcObject.setExchangeRateSetting(false);
                    }
                }
            }
        }
        //检查主服务或者必选服务必须有一个设置过价格
        if (!CollectionUtils.isEmpty(calcObject.getResult()) && !StringUtils.isEmpty(calcObject.getOrderNo())) {
            boolean x = false;
            for (QuotationCalcResult calcResult : calcObject.getResult()) {
                if (!calcResult.isOptional() && calcResult.isHasResult()) {
                    x = true;
                }
            }
            if (!x) {
                calcObject.getResult().clear();
                throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_NOMAINSERVICEPRICE, calcObject, "");
            }
        }
    }

    /**
     * 对报价单集合中的数据进行过滤，经过择优处理后选择一个。
     *
     * @param calcObject 要计算的对象
     * @param sets       报价单集合
     */
    private void matchWellConditions(QuotationCalcObject calcObject, List<QuotationSet> sets) {
        //获取类型(例如：干线、配送、揽收)的map集合
        Map<String, List<ProductServiceRel>> serviceRelMap = new HashMap<>();
        for (Map.Entry<String, ProductServiceRel> entry : calcObject.getProductServiceRelMap().entrySet()) {
            serviceRelMap.putIfAbsent(entry.getValue().getServiceTypeCode(), new ArrayList<>());
            serviceRelMap.get(entry.getValue().getServiceTypeCode()).add(entry.getValue());
        }

        //如果类型(例如：干线、配送、揽收)有一个，那么就不做择优处理，所以从集合中移出
        List<String> removeRelList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(serviceRelMap)) {
            for (String serviceTypeCode : serviceRelMap.keySet()) {
                //得到每个key多对用value的值
                List<ProductServiceRel> productServiceRelList = serviceRelMap.get(serviceTypeCode);
                if (!CollectionUtils.isEmpty(productServiceRelList)) {
                    if (productServiceRelList.size() > 1) {
                        ProductServiceRel rel = orderServiceAssignService.filterWellConditions(productServiceRelList, calcObject);
                        productServiceRelList.remove(rel);
                        removeRelList.addAll(productServiceRelList.stream().map(ProductServiceRel::getServiceUid).collect(Collectors.toList()));
                    }
                }

            }
        }

        Iterator<QuotationSet> it = sets.iterator();
        while (it.hasNext()) {
            QuotationSet serviceSet = it.next();
            if (removeRelList.contains(serviceSet.getServiceUid())) {
                it.remove();
            }
        }
    }


    private void calcSaleResult(QuotationCalcObject calcObject, Region regionMatch, Quotation quotationMatch, QuotationSet set) {
        QuotationCalcResult result = calcObject.addAndGetNewResult();
        result.setGroupId(calcObject.getGroupId());
        result.setRegionId(regionMatch == null ? -1 : regionMatch.getId());
        result.setQuotationId(quotationMatch.getId());
        result.setQuotationGradeType(quotationMatch.getGradeType());
        result.setQuotationGrade(quotationMatch.getGrade());
        result.setQuotationName(quotationMatch.getName());
        result.setQuotationCode(quotationMatch.getCode());
        result.setQuotationUid(quotationMatch.getUid());
        result.setProductName(calcObject.getProductName());
        result.setProductEstimatedTime(calcObject.getProductEstimatedTime());
        result.setProductEstimatedUnit(calcObject.getProductEstimatedUnit());
        result.setSettlementId(quotationMatch.getSettlementTypeId() == null ? -1 : quotationMatch.getSettlementTypeId());
        result.setProductId(calcObject.getProductId());
        if (calcObject.getProductServiceRelMap().get(set.getServiceUid()) != null) {
            ProductServiceRel rel = calcObject.getProductServiceRelMap().get(set.getServiceUid());
            result.setServiceTypCode(rel.getServiceTypeCode());

            List<SysDict> dictList = sysDictService.findByCatalog("biz.service.type");
            dictList.forEach(sysDict -> {
                if (sysDict.getCode().equals(rel.getServiceTypeCode())) {
                    result.setServiceTypCodeName(sysDict.getName());
                }
            });

            result.setOptional("OPTIONAL".equals(rel.getServiceProperty().getCode()));
        }
        result.setFeeTypeId(set.getFeeTypeId());
        result.setCurrencyCode(quotationMatch.getCurrencyCode());


        result.setProductUid(quotationMatch.getBizUid());
        Service service = serviceService.findActivedByUid(set.getServiceUid());
        result.setServiceName(service.getName());
        result.setServiceId(service.getId());
        result.setServiceUid(set.getServiceUid());
        calcObject.putMsg("--->计算服务:" + service.getName() + "的销售价");
        calcFreight(calcObject, regionMatch, set, result);
    }

    private void calcCostResult(QuotationCalcObject calcObject, String serviceUid) {
        calcObject.putMsg("开始计算服务" + serviceUid + "的采购成本价");
        Service service = serviceService.findActivedByUid(serviceUid);
        calcObject.putMsg("服务名称：" + service.getName());
        List<Quotation> quotationCosts = quotationService.findByBizUidAndTime(serviceUid, calcObject.getOrderDate());
        getQuotationMsg(quotationCosts, calcObject);

        //1.过滤货物类型
        calcObject.putMsg("过滤货物类型开始");
        filterCargoType(calcObject, quotationCosts);
        //2.过滤起点
        calcObject.putMsg("过滤起点开始");
        //过滤起点,quotationLeft为空方法内部报异常
        matchStarts(quotationCosts, calcObject);

        //获取月结+人民币的
        Quotation quotationCostMatch = null;
        for (Quotation quotation : quotationCosts) {
            if (CNY.equals(quotation.getCurrencyCode()) &&
                    quotation.getSettlementTypeId() != null &&
                    quotation.getSettlementTypeId() == 1) {
                quotationCostMatch = quotation;
                calcObject.putMsg("匹配到月结+人民币的报价,报价id" + quotationCostMatch.getId());
                break;
            } else {
                calcObject.putMsg("过滤非月结且非人民币的报价,报价id" + quotation.getId());
            }
        }
        if (quotationCostMatch == null) {
            BizException exception = Exceptions.bizException(ErrorCodes.ERROR_QUOTATION_CALC_NOMATCHED, "");
            exception.setData(calcObject);
            throw exception;
        }
        calcCostResult(calcObject, quotationCostMatch, service);
    }

    private void calcCostResult(QuotationCalcObject calcObject, Quotation quotationCostMatch, Service service) {
        calcObject.putMsg("-------->服务" + service.getUid() + "的采购成本价");
        QuotationCalcResult result = calcObject.addAndGetNewResult();
        result.setGroupId(calcObject.getGroupId());
        result.setQuotationId(quotationCostMatch.getId());
        result.setQuotationGradeType(quotationCostMatch.getGradeType());
        result.setQuotationGrade(quotationCostMatch.getGrade());
        result.setQuotationName(quotationCostMatch.getName());
        result.setQuotationCode(quotationCostMatch.getCode());
        result.setQuotationUid(quotationCostMatch.getUid());
        result.setProductName(calcObject.getProductName());
        result.setProductId(calcObject.getProductId());
        result.setCurrencyCode(quotationCostMatch.getCurrencyCode());
        result.setServiceUid(quotationCostMatch.getBizUid());
        result.setServiceName(service.getName());
        result.setServiceId(service.getId());
        result.setSettlementId(
                quotationCostMatch.getSettlementTypeId() == null ? -1 : quotationCostMatch.getSettlementTypeId());
        //匹配目的分区
        List<ServiceTypeRel> types = serviceTypeRelService.findByRefId(service.getId());

        ServiceTypeRel stype = types.get(0);
        calcObject.putMsg("匹配采购成本服务类型" + stype.getServiceTypeCode() + "的分区,条件:regionSchemaUid:" + quotationCostMatch.getRegionSchemaUid() +
                ",date:" + DateUtils.formatDetailDate(calcObject.getOrderDate()));
        int regionId = matchRegion(stype.getServiceTypeCode(), quotationCostMatch.getRegionSchemaUid(), calcObject, quotationCostMatch.getId());

        Region regionCostMatch;
        if (regionId != -1) {
            regionCostMatch = regionService.findById(regionId);
            calcObject.putMsg("匹配到采购成本目标分区uid:" + regionCostMatch.getUid());
        } else {
            regionCostMatch = new Region();
            regionCostMatch.setId(regionId);
            regionCostMatch.setUid(regionId + "");
        }
        result.setRegionId(regionCostMatch.getId());

        List<QuotationSet> setCosts = quotationSetService.findByQuotationId(quotationCostMatch.getId());
        for (QuotationSet setCost : setCosts) {
            calcObject.putMsg("--->计算供应商id:" + setCost.getSupplierId() + "的采购成本价");
            result.setFeeTypeId(setCost.getFeeTypeId());
            result.setSupplierId(setCost.getSupplierId());
            calcFreight(calcObject, regionCostMatch, setCost, result);
        }
    }

    private void calcFreight(QuotationCalcObject calcObject, Region regionMatch,
                             QuotationSet set, QuotationCalcResult result) {
        //已按startPoint升序
        WeightSchemaDto weightSchemaDto = null;
        if (!StringUtils.isEmpty(set.getWeightSchemaUid()) && !NULLUID.equals(set.getWeightSchemaUid())) {
            weightSchemaDto = weightSchemaService.findActivedByUid(set.getWeightSchemaUid());
        }

        result.setWeightSchemaId(weightSchemaDto == null ? -1 : weightSchemaDto.getId());
        List<WeightSection> weightSections = null;
        if (weightSchemaDto != null) {
            weightSections = weightSchemaDto.getWeightSectionList();
            result.setWeightSections(weightSections);
        }

        result.setFeeWeight(calcObject.getCalcWeight());
        result.setFeeWeightUnitCode(calcObject.getCalcWeightUnit());
        //已经按重量段升序排序
        List<FreightItem> freightItems =
                quotationItemService.findByQuotationSetIdAndRegionUid(set.getId(), regionMatch == null ? "-1" : regionMatch.getUid());
        calcObject.putMsg("目标分区价格设置：");
        for (int index = 0; index < freightItems.size(); index++) {
            FreightItem item = freightItems.get(index);
            if (weightSections != null) {
                WeightSection ws = weightSections.get(index);
                String unit = weightSchemaDto.getUnit();
                calcObject.putMsg(ws.getStartPoint() + unit + "~" + ws.getEndPoint() + unit + ":" +
                        getFreightSetting(item));
            } else {
                //没有设置重量段
                calcObject.putMsg("匹配全部重量" + ":" + getFreightSetting(item));
            }
        }
        result.setFreightItems(freightItems);
        //开始计算金额的逻辑
        try {
            calcFreightItem(calcObject, result, weightSchemaDto == null ? KG : weightSchemaDto.getUnit());
            calcObject.putMsg("<------------------------>");
        } catch (BizException bize) {
            //do nothing
            calcObject.putMsg(bize.getMsg());
            if (calcObject.isTry()) {
                //重量段不存在，主必失败则整单计算失败
                if (bize.getErrorCode() == ErrorCodes.ERROR_QUOTATION_CALC_NOMATCHED_WEIGHT.getCode()) {
                    if (!result.isOptional()) {
                        calcObject.getResult().clear();
                        throw bize;
                    }
                }
                //价格设置不存在，主必失败则整单计算失败
                if (bize.getErrorCode() == ErrorCodes.ERROR_QUOTATION_CALC_FREIGHTNOTSET.getCode()) {
                    if (!result.isOptional()) {
                        calcObject.getResult().clear();
                        throw bize;
                    }
                }
            } else {
                //重量段不存在，则整单计算失败
                if (bize.getErrorCode() == ErrorCodes.ERROR_QUOTATION_CALC_NOMATCHED_WEIGHT.getCode()) {
                    if (result.isOptional()) {
                        calcObject.getResult().clear();
                        throw bize;
                    }
                }
                //价格设置
                if (bize.getErrorCode() == ErrorCodes.ERROR_QUOTATION_CALC_FREIGHTNOTSET.getCode()) {
                    if (result.isOptional()) {
                        calcObject.getResult().clear();
                        throw bize;
                    }
                }
            }
        }
    }

    private Quotation filterSaleQuotation(List<Quotation> quotations, QuotationCalcObject calcObject) {
        checkMatchedQuotations(quotations, calcObject);
        getQuotationMsg(quotations, calcObject);

        calcObject.putMsg("匹配报价单过程:");
        //如果是销售价，匹配到协议价非当前客户，则过滤掉 && 过滤掉不匹配的客户币种
        Quotation quotationMatch;
        List<Quotation> quotationLeft = new ArrayList<>();
        quotationLeft.addAll(quotations);
        //匹配销售价
        //2.过滤非当前客户的协议价
        calcObject.putMsg("过滤非当前客户的协议价开始");
        for (int index = quotationLeft.size() - 1; index >= 0; index--) {
            Quotation quotation = quotationLeft.get(index);
            if (quotation.getGradeType() == 2 && quotation.getCustomerId() != calcObject.getCustomerId()) {
                quotationLeft.remove(index);
                calcObject.putMsg("过滤掉非当前客户的协议价:报价id:" + quotation.getId());
            }
        }

        //3.过滤货物类型
        calcObject.putMsg("过滤货物类型开始");
        filterCargoType(calcObject, quotationLeft);
        //4.过滤起点
        calcObject.putMsg("过滤起点开始");
        //过滤起点,quotationLeft为空方法内部报异常
        matchStarts(quotationLeft, calcObject);

        //匹配销售价
        //5.匹配结算方式
        calcObject.putMsg("过滤结算方式开始");
        for (int index = quotationLeft.size() - 1; index >= 0; index--) {
            Quotation quotation = quotationLeft.get(index);
            if (quotation.getSettlementTypeId() != null) {
                if (quotation.getSettlementTypeId() != calcObject.getSettlementId() &&
                        calcObject.getCustomerId() != 0) {
                    calcObject.putMsg("结算方式不匹配-过滤掉报价单Id:" + quotation.getId());
                    quotationLeft.remove(index);
                }
            }
        }
        //公共变量
        List<Quotation> orderedQuotation = new ArrayList<>();
        //起点包含，结算方式相同的报价
        orderedQuotation.add(null);
        //起点包含，结算方式空的报价
        orderedQuotation.add(null);
        //起点空，结算方式相同的报价
        orderedQuotation.add(null);
        //起点空，结算方式空的报价
        orderedQuotation.add(null);
        List<Quotation> quotationCNYs = new ArrayList<>();
        quotationCNYs.add(null);
        quotationCNYs.add(null);
        quotationCNYs.add(null);
        quotationCNYs.add(null);
        //6.匹配唯一的价格
        //销售价匹配唯一的报价
        //按 “协议价<--套餐价<---等级价等级最高” 匹配
        //6.1协议价
        calcObject.putMsg("---->开始匹配协议价");
        quotationLeft
                .stream()
                .filter(q -> q.getGradeType() == 2)
                .forEach(q -> handlerOrderedSaleQuotation(calcObject, orderedQuotation, quotationCNYs, q));
        quotationMatch = matchWeakAndCNYQuotation(calcObject, orderedQuotation, quotationCNYs);
        //6.2套餐价
        if (quotationMatch == null) {
            calcObject.putMsg("---->开始匹配套餐价");
            clearQuotations(orderedQuotation, quotationCNYs);
            if (!StringUtils.isEmpty(calcObject.getComboCode())) {
                List<PriceComboQuotation> comboQuotations =
                        priceComboService.findPriceQuotation(calcObject.getComboCode());
                for (Quotation q : quotationLeft) {
                    comboQuotations.stream()
                            .filter(cq ->
                                    cq.getProductUid().equals(q.getBizUid()) &&
                                            q.getGradeType() == 1 &&
                                            cq.getPriceQuotationGrade().intValue() == q.getGrade())
                            .forEach(cq ->
                                    handlerOrderedSaleQuotation(calcObject, orderedQuotation, quotationCNYs, q));
                }
            }
            quotationMatch = matchWeakAndCNYQuotation(calcObject, orderedQuotation, quotationCNYs);
        }
        //6.3等级价的取最高等级1<-2<-3<-4<-5
        if (quotationMatch == null) {
            //一级
            calcObject.putMsg("---->开始匹配一级等级价");
            clearQuotations(orderedQuotation, quotationCNYs);
            quotationLeft
                    .stream()//等级类型getGradeType：1等级价2.协议价   getGrade：价格等级
                    .filter(quotation -> quotation.getGradeType() == 1 && quotation.getGrade() == 1)
                    .forEach(q -> handlerOrderedSaleQuotation(calcObject, orderedQuotation, quotationCNYs, q));
            quotationMatch = matchWeakAndCNYQuotation(calcObject, orderedQuotation, quotationCNYs);

            //二级
            if (quotationMatch == null) {
                calcObject.putMsg("---->开始匹配二级等级价");
                clearQuotations(orderedQuotation, quotationCNYs);
                quotationLeft
                        .stream()
                        .filter(q -> q.getGradeType() == 1 && q.getGrade() == 2)
                        .forEach(q -> handlerOrderedSaleQuotation(calcObject, orderedQuotation, quotationCNYs, q));
                quotationMatch = matchWeakAndCNYQuotation(calcObject, orderedQuotation, quotationCNYs);

            }
            //三级
            if (quotationMatch == null) {
                calcObject.putMsg("---->开始匹配三级等级价");
                clearQuotations(orderedQuotation, quotationCNYs);
                quotationLeft
                        .stream()
                        .filter(q -> q.getGradeType() == 1 && q.getGrade() == 3)
                        .forEach(q -> handlerOrderedSaleQuotation(calcObject, orderedQuotation, quotationCNYs, q));
                quotationMatch = matchWeakAndCNYQuotation(calcObject, orderedQuotation, quotationCNYs);

            }
            //四级
            if (quotationMatch == null) {
                calcObject.putMsg("---->开始匹配四级等级价");
                clearQuotations(orderedQuotation, quotationCNYs);
                quotationLeft
                        .stream()
                        .filter(q -> q.getGradeType() == 1 && q.getGrade() == 4)
                        .forEach(q -> handlerOrderedSaleQuotation(calcObject, orderedQuotation, quotationCNYs, q));
                quotationMatch = matchWeakAndCNYQuotation(calcObject, orderedQuotation, quotationCNYs);

            }
            //五级
            if (quotationMatch == null) {
                calcObject.putMsg("---->开始匹配五级等级价");
                clearQuotations(orderedQuotation, quotationCNYs);
                quotationLeft
                        .stream()
                        .filter(q -> q.getGradeType() == 1 && q.getGrade() == 5)
                        .forEach(q -> handlerOrderedSaleQuotation(calcObject, orderedQuotation, quotationCNYs, q));
                quotationMatch = matchWeakAndCNYQuotation(calcObject, orderedQuotation, quotationCNYs);
            }
        }


        if (quotationMatch == null) {
            BizException exception = Exceptions.bizException(ErrorCodes.ERROR_QUOTATION_CALC_NOMATCHED, "");
            exception.setData(calcObject);
            throw exception;
        }

        return quotationMatch;
    }

    private void filterCargoType(QuotationCalcObject calcObject, List<Quotation> quotations) {
        for (int index = quotations.size() - 1; index >= 0; index--) {
            Quotation quotation = quotations.get(index);
            if (!quotation.getCargoTypeCode().equals(calcObject.getCargoTypeCode())) {
                quotations.remove(index);
                calcObject.putMsg("货物类型不匹配-过滤掉报价单id:" + quotation.getId());
            }
        }
    }

    private void clearQuotations(List<Quotation> orderedQuotation, List<Quotation> quotationCNYs) {
        orderedQuotation.set(0, null);
        orderedQuotation.set(1, null);
        orderedQuotation.set(2, null);
        orderedQuotation.set(3, null);

        quotationCNYs.set(0, null);
        quotationCNYs.set(1, null);
        quotationCNYs.set(2, null);
        quotationCNYs.set(3, null);
    }

    /**
     * 匹配结算方式交易币种
     */
    private Quotation matchWeakAndCNYQuotation(QuotationCalcObject calcObject, List<Quotation> orderedQuotation,
                                               List<Quotation> quotationCNYs) {
        Quotation quotationMatch = null;
        //起点相同的报价
        if (orderedQuotation.get(0) != null) {
            quotationMatch = orderedQuotation.get(0);
        }
        if (quotationMatch == null && quotationCNYs.get(0) != null) {
            quotationMatch = quotationCNYs.get(0);
        }

        //结算方式相同的报价
        if (quotationMatch == null && orderedQuotation.get(1) != null) {
            quotationMatch = orderedQuotation.get(1);
        }
        if (quotationMatch == null && quotationCNYs.get(1) != null) {
            quotationMatch = quotationCNYs.get(1);
        }

        //匹配客户的交易币种
        if (quotationMatch == null && orderedQuotation.get(2) != null) {
            quotationMatch = orderedQuotation.get(2);
        }
        if (quotationMatch == null && quotationCNYs.get(2) != null) {
            quotationMatch = quotationCNYs.get(2);
        }

        if (quotationMatch == null && orderedQuotation.get(3) != null) {
            quotationMatch = orderedQuotation.get(3);
        }
        if (quotationMatch == null && quotationCNYs.get(3) != null) {
            quotationMatch = quotationCNYs.get(3);
        }
        if (quotationMatch != null) {
            calcObject.putMsg("匹配到报价,报价id" + quotationMatch.getId());
        }
        return quotationMatch;
    }

    /**
     * @param saleCalcObject   计算销售价对象
     * @param orderedQuotation 公共变量orderedQuotation
     * @param quotationCNYs    人民币的报价
     * @param quotation        价格方案
     */
    private void handlerOrderedSaleQuotation(QuotationCalcObject saleCalcObject, List<Quotation> orderedQuotation,
                                             List<Quotation> quotationCNYs, Quotation quotation) {
        if (quotation instanceof QuotationDto) {
            QuotationDto dto = (QuotationDto) quotation;
            //起点匹配的
            if (!CollectionUtils.isEmpty(dto.getQuotationStarts())) {
                //结算方式不空
                if (dto.getSettlementTypeId() != null) {
                    saleCalcObject.putMsg("匹配到起点包含，结算方式相同的报价" + quotation.getId());
                    //处理交易货币
                    matchCurrency(0, saleCalcObject, quotationCNYs, quotation, orderedQuotation);
                } else {
                    saleCalcObject.putMsg("匹配到起点包含，结算方式空的报价" + quotation.getId());
                    //处理交易货币
                    matchCurrency(1, saleCalcObject, quotationCNYs, quotation, orderedQuotation);
                }
            } else {
                //结算方式不空
                if (dto.getSettlementTypeId() != null) {
                    saleCalcObject.putMsg("匹配到起点空，结算方式相同的报价" + quotation.getId());
                    //处理交易货币
                    matchCurrency(2, saleCalcObject, quotationCNYs, quotation, orderedQuotation);
                } else {
                    saleCalcObject.putMsg("匹配到起点空，结算方式空的报价" + quotation.getId());
                    //处理交易货币
                    matchCurrency(3, saleCalcObject, quotationCNYs, quotation, orderedQuotation);
                }
            }
        }
    }

    /**
     * @param index            索引
     * @param saleCalcObject   计算销售价对象
     * @param quotationCNYs    人民币的报价集合
     * @param quotation        价格方案
     * @param orderedQuotation 公共变量orderedQuotation
     */
    private void matchCurrency(int index, QuotationCalcObject saleCalcObject, List<Quotation> quotationCNYs,
                               Quotation quotation, List<Quotation> orderedQuotation) {
        //交易货币
        Quotation quotationMatch = null;
        //过滤客户的币种
        if (!StringUtils.isEmpty(quotation.getCurrencyCode()) &&
                !StringUtils.isEmpty(saleCalcObject.getCustCurrencyCode()) &&
                quotation.getCurrencyCode().equalsIgnoreCase(saleCalcObject.getCustCurrencyCode())) {
            quotationMatch = quotation;
        } else {
            //当前客户未登录
            if (saleCalcObject.getCustomerId() == 0) {
                //价格方案如果是人民币结算
                if (CNY.equals(quotation.getCurrencyCode())) {
                    //查找匹配报价已按settlementId desc排序 优先月结 再是空的 不匹配结算方式大于1的 1代表月结
                    boolean isRmbMonth = quotationCNYs.get(index) != null && quotationCNYs.get(index).getSettlementTypeId() == 1;
                    boolean isThisNotMonth = quotation.getSettlementTypeId() != null && quotation.getSettlementTypeId() > 1;
                    if (isRmbMonth || isThisNotMonth) {
                        return;
                    }
                    saleCalcObject.putMsg("------------->人民币的报价" + quotation.getId());
                    quotationCNYs.set(index, quotation);
                    return;
                }
            } else {
                if (CNY.equals(quotation.getCurrencyCode())) {
                    if (quotationCNYs.get(index) != null) {
                        BizException exception =
                                Exceptions.bizException(ErrorCodes.ERROR_QUOTATION_CALC_MATCHMUILT, 2);
                        exception.setData(saleCalcObject);
                        throw exception;
                    }
                    saleCalcObject.putMsg("------------->人民币的报价" + quotation.getId());
                    quotationCNYs.set(index, quotation);
                    return;
                }
            }
        }
        if (quotationMatch != null) {
            if (saleCalcObject.getCustomerId() == 0) {
                //查找匹配报价已按settlementId desc排序 优先月结 再是空的 不匹配结算方式大于1的
                boolean orderedHasMonth = orderedQuotation.get(index) != null && orderedQuotation.get(index).getSettlementTypeId() == 1;
                boolean thisNotMonth = quotation.getSettlementTypeId() != null && quotation.getSettlementTypeId() > 1;
                if (orderedHasMonth || thisNotMonth) {
                    return;
                }
                saleCalcObject.putMsg("------------->相同货币的报价：" + quotation.getId());
                orderedQuotation.set(index, quotation);
            } else {
                if (orderedQuotation.get(index) != null) {
                    BizException exception =
                            Exceptions.bizException(ErrorCodes.ERROR_QUOTATION_CALC_MATCHMUILT, 2);
                    exception.setData(saleCalcObject);
                    throw exception;
                }
                saleCalcObject.putMsg("------------->相同货币的报价：" + quotation.getId());
                orderedQuotation.set(index, quotationMatch);
            }
        } else {
            saleCalcObject.putMsg("报价" + quotation.getId() + "的货币为" +
                    quotation.getCurrencyCode() + "与" + (saleCalcObject.getCustomerId() == 0 ? "传入的" : "客户的") +
                    saleCalcObject.getCustCurrencyCode() + "不匹配且不为CNY");
        }
    }

    private void calcFreightItem(QuotationCalcObject calcObject, QuotationCalcResult result, String weightSchemaUnit) {
        List<WeightSection> weightSections = result.getWeightSections();
        int index = -1;
        BigDecimal realWeight = BigDecimal.ZERO;
        String prefix = "计算报价时(calcObject.calcWeightUnit):";
        try {
            realWeight = unitConversionService.getTargetWeight(calcObject.getCalcWeight(),
                    calcObject.getCalcWeightUnit(), weightSchemaUnit);
        } catch (BizException e) {
            throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMsg());
        } catch (Exception e) {
            throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMessage());
        }
        calcObject.putMsg("换算成重量段设置的计量单位,重量变为：" + realWeight.doubleValue() + weightSchemaUnit);

        if (weightSections != null) {
            for (WeightSection weight : weightSections) {
                if (realWeight.compareTo(weight.getStartPoint()) > 0 &&
                        realWeight.compareTo(weight.getEndPoint()) <= 0) {
                    index = weightSections.indexOf(weight);
                    calcObject.putMsg("匹配到重量段" + weight.getStartPoint() + "~" + weight.getEndPoint());
                }
            }
        }

        if (index == -1 && !org.springframework.util.CollectionUtils.isEmpty(weightSections)) {
            BizException exception = Exceptions.bizException(ErrorCodes.ERROR_QUOTATION_CALC_NOMATCHED_WEIGHT, "");
            exception.setData(calcObject);
            throw exception;
        }
        if (index == -1) {
            index = 0;
        }
        //匹配到的价格设置
        FreightItem fi = result.getFreightItems().get(index);

        //没有设置就算不出来价格
        if (fi == null) {
            BizException exception = Exceptions.bizException(ErrorCodes.ERROR_QUOTATION_CALC_NOMATCHED_WEIGHT, "");
            exception.setData(calcObject);
            throw exception;
        }
        if (fi.getPrice() == null || fi.getPrice().signum() < 0) {
            BizException exception = Exceptions.bizException(ErrorCodes.ERROR_QUOTATION_CALC_FREIGHTNOTSET, "price");
            exception.setData(calcObject);
            throw exception;
        }

        //非总价才会设置基值
        if (!CalcTypeEnum.TOTALPRICE.getCode().equals(fi.getCalcType())) {
            if (fi.getBaseValue().signum() < 0) {
                throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_FREIGHTNOTSET, calcObject, "baseValue");
            }
            if (StringUtils.isEmpty(fi.getBaseUnitCode())) {
                throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_FREIGHTNOTSET, calcObject, "baseUnitCode");
            }
        }

        calcObject.putMsg("匹配到价格设置:" + getFreightSetting(fi));
        switch (fi.getCalcType()) {
            case "PCT_SUM":
                calcObject.putMsg("重量落在总价：" + fi.getPrice().doubleValue());
                result.setAmount(fi.getPrice());
                break;
            case "PCT_PRICE":
                calcObject.putMsg("重量落在单价:" + getFreightSetting(fi));
                BigDecimal amountPCT002 = calcSinglePrice(fi, calcObject, realWeight, weightSchemaUnit);
                result.setAmount(amountPCT002);
                break;
            case "PCT_CONTINUEPRICE":
                calcObject.putMsg("重量落在续单价:" + getFreightSetting(fi));
                BigDecimal amountPCT003 = calcContinuePrice(fi, calcObject, result, realWeight, weightSchemaUnit);
                result.setAmount(amountPCT003);
                break;
            case "PCT_CONTINUESECTIONPRICE":
                calcObject.putMsg("重量落在续阶单价:" + getFreightSetting(fi));
                BigDecimal amountPCT004 =
                        calcContinueSectionPrice(index, fi, calcObject, result, realWeight, weightSchemaUnit);
                result.setAmount(amountPCT004);
                break;
            default:
                break;
        }
        if (result.getAmount() == null) {
            throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_NOAMOUNT, calcObject, "");
        }
        result.setHasResult(true);

    }

    /**
     * 按单价计算
     */
    private BigDecimal calcSinglePrice(FreightItem fi, QuotationCalcObject calcObject,
                                       BigDecimal weight, String weighUnit) {
        //校验单价设置
        if (CalcTypeEnum.TOTALPRICE.getCode().equals(fi.getCalcType())) {
            throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_CALCTYPEERROR, calcObject, "单价,续单价,续阶单价", "总价");
        }
        //重量值的换算关系
        BigDecimal realWeight = weight;
        if (!fi.getBaseUnitCode().equals(weighUnit)) {
            String prefix = "计算报价单价时(calcObject.calcWeightUnit):";
            try {
                realWeight = unitConversionService.getTargetWeight(weight, weighUnit, fi.getBaseUnitCode());
            } catch (BizException e) {
                throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMsg());
            } catch (Exception e) {
                throwCalcException(ErrorCodes.ERROR_COMMON, calcObject, prefix + e.getMessage());
            }
            calcObject.putMsg("换算成设置的计量单位,重量变为：" + realWeight.doubleValue() + fi.getBaseUnitCode());
        }

        //向上进位
        BigDecimal realCalcUnit = realWeight.divide(fi.getBaseValue(), 0, BigDecimal.ROUND_UP);
        calcObject.putMsg("换算成设置的计价单位,乘单价的重量值变为：" +
                realWeight.doubleValue() + "÷" + fi.getBaseValue().doubleValue() +
                "=(小数点后保留0位，向上取整)" + realCalcUnit.intValue());
        BigDecimal result = realCalcUnit.multiply(fi.getPrice());
        calcObject.putMsg("计算出金额：" + realCalcUnit.doubleValue() + "*" +
                fi.getPrice().doubleValue() + "=" + result.doubleValue());
        //计算价格
        return result;
    }

    /**
     * 按续单价计算
     * 下面的计算逻辑有个隐含的逻辑：
     * 1.续单价的设置肯定不是第一个，如果续单价是第一个，则下面的逻辑可定会报异常
     */
    private BigDecimal calcContinuePrice(FreightItem fi, QuotationCalcObject calcObject,
                                         QuotationCalcResult result, BigDecimal realWeight, String weightSchemaUnit) {
        if (!CalcTypeEnum.CONTINUEPRICE.getCode().equals(fi.getCalcType())) {
            throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_CALCTYPEERROR, calcObject, "");
        }
        List<WeightSection> weightSections = result.getWeightSections();
        List<FreightItem> freightItems = result.getFreightItems();
        int fiIndex = freightItems.indexOf(fi);
        //第一个不能为续单价
        if (fiIndex == 0) {
            calcObject.putMsg("续单价不能作为每个分区第一个价格设置");
            throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR, calcObject, "第一个单价不能为续单价");
        }
        WeightSection firstWeigth = weightSections.get(fiIndex - 1);
        FreightItem firstFreightItem = freightItems.get(fiIndex - 1);
        if (!CalcTypeEnum.TOTALPRICE.getCode().equals(firstFreightItem.getCalcType())) {
            String msg = "续单价上一个价格设置必须为总价,实际为:" + firstFreightItem.getCalcType();
            calcObject.putMsg(msg);
            throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR, calcObject, msg);
        }

        calcObject.putMsg("匹配到续单价首重,设置明细:" + getFreightSetting(firstFreightItem));
        BigDecimal firstAmount = firstFreightItem.getPrice();

        //计算续单价的剩余重金额
        BigDecimal leftWeight = realWeight.subtract(firstWeigth.getEndPoint());
        if (leftWeight.signum() <= 0) {
            throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_WEIGHTERROR, calcObject, "计算完首重之后,剩余重量变为："
                    + realWeight + "-" + firstWeigth.getEndPoint() + "=" + leftWeight.doubleValue());
        }
        calcObject.putMsg("计算完首重之后,剩余重量变为："
                + realWeight.doubleValue() + "-" + firstWeigth.getEndPoint() + "=" + leftWeight.doubleValue());
        calcObject.putMsg("计算续单价:");
        BigDecimal leftWeightAmount = calcSinglePrice(fi, calcObject, leftWeight, weightSchemaUnit);
        BigDecimal res = firstAmount.add(leftWeightAmount);
        calcObject.putMsg("计算出总金额：" + firstAmount + "+" + leftWeightAmount.doubleValue() + "=" + res.doubleValue());
        return res;
    }

    /**
     * 按续阶单价计算
     */
    private BigDecimal calcContinueSectionPrice(int freightIndex, FreightItem fi, QuotationCalcObject calcObject,
                                                QuotationCalcResult result, BigDecimal realWeight,
                                                String weightSchemaUnit) {
        if (!CalcTypeEnum.CONTINUESECTIONPRICE.getCode().equals(fi.getCalcType())) {
            throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_CALCTYPEERROR, calcObject,
                    CalcTypeEnum.CONTINUESECTIONPRICE.getCode(), fi.getCalcType());
        }
        List<FreightItem> freightItems = result.getFreightItems();
        List<WeightSection> weights = result.getWeightSections();
        //检查各个前置阶段的设置是否正确
        List<Integer> sectionIndexList = new ArrayList<>();
        //从续阶单价的上一个开始价格设置起算
        for (int index = freightIndex - 1; index >= 0; index--) {
            FreightItem freightItem = freightItems.get(index);
            if (index == freightIndex - 1 && freightItem.getCalcType().equals(CalcTypeEnum.TOTALPRICE.getCode())) {
                String msg = "续阶单价与总价之间必须存在单价";
                calcObject.putMsg(msg);
                throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR, calcObject, msg);
            }
            sectionIndexList.add(0, index);
            if (freightItem.getCalcType().equals(CalcTypeEnum.TOTALPRICE.getCode())) {
                break;
            }
            if (!freightItem.getCalcType().equals(CalcTypeEnum.PRICE.getCode())) {
                String msg = "续阶单价与总价之间必须为单价,实际为:" + freightItem.getCalcType();
                calcObject.putMsg(msg);
                throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR, calcObject, msg);
            }
        }

        if (!freightItems.get(sectionIndexList.get(0)).getCalcType().equals(CalcTypeEnum.TOTALPRICE.getCode())) {
            String msg = "续阶单价没有找到前置的总价";
            calcObject.putMsg(msg);
            throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR, calcObject, msg);
        }

        //记录每个阶段计算出的综合  金额 + 已经计算过的重量
        BigDecimal sectionTotalAmount = BigDecimal.ZERO;
        BigDecimal sectionTotalWeight = BigDecimal.ZERO;
        //记录上次计算的末尾重量
        BigDecimal lastSectionWeight = BigDecimal.ZERO;

        // 下面的IDEA提示修改为foreach请不要修改，自动修改后的结果是错的
        for (int index = 0; index < sectionIndexList.size(); index++) {
            int realIndex = sectionIndexList.get(index);
            FreightItem freightItem = freightItems.get(realIndex);

            //获取重量段
            WeightSection weightSection = weights.get(realIndex);

            calcObject.putMsg("使用重量段计算续阶单价："
                    + weightSection.getStartPoint().doubleValue() + "~" + weightSection.getEndPoint().doubleValue());

            BigDecimal sectionWeight = weightSection.getEndPoint().subtract(lastSectionWeight);
            sectionTotalWeight = sectionTotalWeight.add(sectionWeight);
            BigDecimal sectionAmount;
            //计算续阶单价的前置段金额
            if (CalcTypeEnum.PRICE.getCode().equals(freightItem.getCalcType())) {
                sectionAmount = calcSinglePrice(freightItem, calcObject, sectionWeight, weightSchemaUnit);
                calcObject.putMsg("使用单价计算阶段价的方式计算出此段金额：" + sectionAmount.doubleValue());
            } else {
                sectionAmount = freightItem.getPrice();
                calcObject.putMsg("使用总价计算阶段价的方式计算出此段金额：" + sectionAmount.doubleValue());
            }
            lastSectionWeight = weightSection.getEndPoint();
            sectionTotalAmount = sectionTotalAmount.add(sectionAmount);
        }
        //计算续阶单价的剩余金额
        BigDecimal leftWeight = realWeight.subtract(sectionTotalWeight);
        if (leftWeight.signum() <= 0) {
            throwCalcException(ErrorCodes.ERROR_QUOTATION_CALC_WEIGHTERROR, calcObject,
                    "计算完续阶重之后,剩余重量变为：" + leftWeight.doubleValue());
        }
        BigDecimal leftWeightAmount = calcSinglePrice(fi, calcObject, leftWeight, weightSchemaUnit);
        calcObject.putMsg("使用单价计算阶段价的方式计算出剩余金额：" + leftWeightAmount.doubleValue());
        return leftWeightAmount.add(sectionTotalAmount);
    }

    /**
     * 匹配起点
     *
     * @param quotationLeft quotationLeft
     * @param calcObject    calcObject
     */
    private void matchStarts(List<Quotation> quotationLeft, QuotationCalcObject calcObject) {
        if (CollectionUtils.isEmpty(quotationLeft)) {
            return;
        }
        List<Integer> quotationIds = EntityUtils.getIntPropListFromBeans(quotationLeft, "id");
        //已按国家 国内城市 国外城市 邮编 升序排序
        List<QuotationStart> starts = quotationStartService.findByQuotationIds(quotationIds);
        //组合成QuotationDto
        List<QuotationDto> quotationDtos = new ArrayList<>();
        for (Quotation quotation : quotationLeft) {
            QuotationDto dto = new QuotationDto();
            BeanUtils.copyProperties(quotation, dto);
            starts.stream()
                    .filter(start -> start.getQuotationId() == quotation.getId())
                    .forEach(start -> dto.getQuotationStarts().add(start));
            quotationDtos.add(dto);
        }
        //5.开始匹配起点

        quotationLeft.clear();
        calcObject.putMsg("开始匹配起点:");
        List<QuotationDto> emptyStarts = new ArrayList<>();
        for (QuotationDto dto : quotationDtos) {

            if (CollectionUtils.isEmpty(dto.getQuotationStarts())) {
                calcObject.putMsg("起点为空的起点，报价单Id:" + dto.getId());
                emptyStarts.add(dto);
                continue;
            }
            boolean matchStart = false;
            for (QuotationStart qs : dto.getQuotationStarts()) {
                //国家
                if (qs.getType() == RegionTypeCode.COUNTRY && qs.getCountry().equals(calcObject.getFromCountry())) {
                    quotationLeft.add(dto);
                    calcObject.putMsg("起点匹配到国家，报价单Id:" + dto.getId());
                    matchStart = true;
                    continue;
                }
                //国内地区
                if (qs.getType() == RegionTypeCode.CNAREA && CN.equals(calcObject.getFromCountry())) {
                    if (qs.getAreaId().equals(calcObject.getFromProvince())) {
                        quotationLeft.add(dto);
                        calcObject.putMsg("起点匹配到中国的省，报价单id:" + dto.getId() + ",报价单省：" + qs.getAreaId());
                        matchStart = true;
                        continue;
                    }
                    if (qs.getAreaId().equals(calcObject.getFromCity())) {
                        quotationLeft.add(dto);
                        calcObject.putMsg("起点匹配到中国的市，报价单id:" + dto.getId() + ",报价单市：" + qs.getAreaId());
                        matchStart = true;
                        continue;
                    }
                    if (qs.getAreaId().equals(calcObject.getFromArea())) {
                        quotationLeft.add(dto);
                        calcObject.putMsg("起点匹配到中国的区，报价单id:" + dto.getId() + ",报价单区：" + qs.getAreaId());
                        matchStart = true;
                        continue;
                    }
                    if (qs.getAreaId().equals(calcObject.getFromStreet())) {
                        quotationLeft.add(dto);
                        calcObject.putMsg("起点匹配到中国的街道，报价单id:" + dto.getId() + ",报价单街道：" + qs.getAreaId());
                        matchStart = true;
                        continue;
                    }
                }
                //国外地区
                if (qs.getType() == RegionTypeCode.FOREIGNAREA &&
                        qs.getCountry().equals(calcObject.getFromCountry()) &&
                        qs.getAreaId().equals(calcObject.getFromCity())) {
                    quotationLeft.add(dto);
                    calcObject.putMsg("起点匹配到国外地区，报价单id:" + dto.getId() + ",报价单国外地区" + qs.getAreaId());
                    matchStart = true;
                }
            }
            if (!matchStart) {
                calcObject.putMsg("起点不匹配，过滤掉报价单Id:" + dto.getId());
            }
        }
        //下面的if仅仅放入消息不做其他用
        quotationLeft.addAll(emptyStarts);
        if (quotationLeft.size() == 0 && emptyStarts.size() > 0) {
            calcObject.putMsg("起点没有匹配到具体始发地的报价单，但存在始发地为空的报价单，以下报价单均为始发地为空的");
            getQuotationMsg(quotationLeft, calcObject);
        }
        checkMatchedQuotations(quotationLeft, calcObject);
    }

    /**
     * 匹配目的分区
     *
     * @param serviceType     serviceType
     * @param regionSchemaUid regionSchemaUid
     * @param calcObject      calcObject
     */
    private int matchRegion(String serviceType, String regionSchemaUid, QuotationCalcObject calcObject, int quotationId) {
        if (StringUtils.isEmpty(regionSchemaUid) || NULLUID.equals(regionSchemaUid)) {
            return -1;
        }
        List<String> regionSchemaUids = new ArrayList<>();
        regionSchemaUids.add(regionSchemaUid);
        List<Region> regions = regionService.findRegionBySchemaUids(regionSchemaUids, true, null);

        List<Integer> regionIds = EntityUtils.getIntPropListFromBeans(regions, "id");
        List<RegionItem> regionItemList = regionItemService.findByRegionIds(regionIds);
        //排序type 1国家 2国内城市 3国外城市 4邮编
        regionItemList.sort(Comparator.comparingInt(RegionItem::getType));

        String matchContry;
        String matchProvince;
        String matchCity;
        String matchArea;
        String matchStreet;
        String matchPostcode;
        switch (serviceType) {
            //揽收地址
            case "ST001":
                matchContry = calcObject.getFetchCountry();
                matchProvince = calcObject.getFetchProvince();
                matchCity = calcObject.getFetchCity();
                matchArea = calcObject.getFetchArea();
                matchStreet = calcObject.getFetchStreet();
                matchPostcode = calcObject.getFetchPostcode();
                break;
            //默认目的地址
            default:
                matchContry = calcObject.getToCountry();
                matchProvince = calcObject.getToProvince();
                matchCity = calcObject.getToCity();
                matchArea = calcObject.getToArea();
                matchStreet = calcObject.getToStreet();
                matchPostcode = calcObject.getToPostcode();
                break;
        }

        int regionId = 0;
        for (RegionItem ri : regionItemList) {
            //国家
            if (ri.getType() == RegionTypeCode.COUNTRY && ri.getCountry().equals(matchContry)) {
                regionId = ri.getRegionId();
                calcObject.putMsg("目的地匹配到国家，分区明细id:" + ri.getId() +
                        ",国家:" + ri.getCountry() + ",报价单id:" + quotationId);
                continue;
            }
            //国内地区
            if (ri.getType() == RegionTypeCode.CNAREA && CN.equals(matchContry)) {
                if (ri.getAreaId().equals(matchProvince)) {
                    regionId = ri.getRegionId();
                    calcObject.putMsg("目的地匹配到中国的省，分区明细id:" + ri.getId() +
                            ",省：" + ri.getAreaId() + ",报价单id:" + quotationId);
                    continue;
                }
                if (ri.getAreaId().equals(matchCity)) {
                    regionId = ri.getRegionId();
                    calcObject.putMsg("目的地匹配到中国的市，分区明细id:" + ri.getId() +
                            ",市：" + ri.getAreaId() + ",报价单id:" + quotationId);
                    continue;
                }
                if (ri.getAreaId().equals(matchArea)) {
                    regionId = ri.getRegionId();
                    calcObject.putMsg("起点匹配到中国的区，分区明细id:" + ri.getId() +
                            ",市：" + ri.getAreaId() + ",报价单id:" + quotationId);
                    continue;
                }
                if (ri.getAreaId().equals(matchStreet)) {
                    regionId = ri.getRegionId();
                    calcObject.putMsg("匹配到中国的街道，分区明细id:" + ri.getId() +
                            ",街道：" + ri.getAreaId() + ",报价单id:" + quotationId);
                    continue;
                }
            }
            //国外地区
            if (ri.getType() == RegionTypeCode.FOREIGNAREA &&
                    ri.getCountry().equals(matchContry) &&
                    ri.getAreaId().equals(matchCity)) {
                regionId = ri.getRegionId();
                calcObject.putMsg("目的地匹配到国外地区，分区明细id:" + ri.getId() +
                        ",国外地区" + ri.getAreaId() + ",报价单id:" + quotationId);
            }
            //邮编
            if (ri.getType() == RegionTypeCode.POSTCODE && matchPostcode != null &&
                    ri.getStartPostcode().compareToIgnoreCase(matchPostcode) <= 0 &&
                    ri.getEndPostcode().compareToIgnoreCase(matchPostcode) >= 0) {
                regionId = ri.getRegionId();
                calcObject.putMsg("目的地匹配到邮编，分区明细id:" + ri.getId() +
                        ",邮编段" + ri.getStartPostcode() + "~" + ri.getEndPostcode() + ",报价单id:" + quotationId);
            }
        }
        if (regionId == 0) {
            calcObject.putMsg("报价单目的地分区不匹配，报价单Id:" + quotationId);
            BizException exception = Exceptions.bizException(ErrorCodes.ERROR_QUOTATION_CALC_NOMATCHED_TERMINAL, "");
            exception.setData(calcObject);
            throw exception;
        }
        return regionId;

    }

    /**
     * 添加计算消息：匹配到的报价单的详细信息
     *
     * @param quotations quotations
     * @param calcObject calcObject
     */
    private void getQuotationMsg(List<Quotation> quotations, QuotationCalcObject calcObject) {
        for (Quotation quotation : quotations) {
            String sb = "code:" + quotation.getCode() + ",id:" + quotation.getId() +
                    ",uid:" + quotation.getUid() +
                    ",name:" + quotation.getName() +
                    ",生效期:" + DateUtils.formatDetailDate(quotation.getStartEffectTime()) +
                    "~" + DateUtils.formatDetailDate(quotation.getEndEffectTime()) +
                    ",分区方案Uid:" + quotation.getRegionSchemaUid() +
                    ",重量段放方案Uid:" + quotation.getWeightSchemaUid() +
                    ",gradeType:" + quotation.getGradeType() +
                    ",grade:" + quotation.getGrade() +
                    ",货物类型:" + quotation.getCargoTypeCode() +
                    ",货币:" + quotation.getCurrencyCode() +
                    ",结算方式:" + (quotation.getSettlementTypeId() == null ? "空" : quotation.getSettlementTypeId());
            calcObject.putMsg(sb);
        }
    }

    /**
     * 检查匹配到的报价单是否为空
     *
     * @param quotations quotations
     * @param calcObject calcObject
     */
    private void checkMatchedQuotations(List<Quotation> quotations, QuotationCalcObject calcObject) {
        if (CollectionUtils.isEmpty(quotations)) {
            BizException exception = Exceptions.bizException(ErrorCodes.ERROR_QUOTATION_CALC_NOMATCHED, "");
            exception.setData(calcObject);
            throw exception;
        }
    }

    /**
     * 获取价格设置明细
     *
     * @param fi fi
     * @return String
     */
    private String getFreightSetting(FreightItem fi) {
        String str = "计价方式:" + fi.getCalcType() + ",价格值:" + fi.getPrice().doubleValue();
        if (!CalcTypeEnum.TOTALPRICE.getCode().equals(fi.getCalcType())) {
            str += ",价格单位:" + fi.getBaseValue() + "/" + fi.getBaseUnitCode();
        }
        return str;
    }

    /**
     * 抛出异常
     *
     * @param code       code
     * @param calcObject calcObject
     * @param params     params
     */
    private void throwCalcException(ErrorCode code, QuotationCalcObject calcObject, String... params) {
        BizException exception = Exceptions.bizException(code, (Object[]) params);
        exception.setData(calcObject);
        throw exception;
    }

    /**
     * 根据订单号算预估费用，并返回预估费用的值与预估费用的单位
     *
     * @param orderNo 订单号
     * @return object[0]=预估费用。object[1]=预估费用单位
     */
    @Override
    public Object[] calcFeeByOrderNo(String orderNo) {
        Object[] result = new Object[2];

        OrdOrder order = orderService.findByOrderNo(orderNo);
        if (ObjectUtils.isEmpty(order)) {
            throw Exceptions.bizException(ErrorCodes.ERROR_COMMON, "订单未找到");
        }
        boolean isFromPre = false;
        if (OrderStatus.RECEGOOD.compareTo(order.getOrderStatus()) > 0) {
            isFromPre = true;
        }
        QuotationCalcObject qco = this.calcOrderSalePrice(orderNo, isFromPre);
        final BigDecimal[] amount = {BigDecimal.ZERO};
        final String[] currency = {""};
        qco.getResult().forEach(item -> {
            currency[0] = item.getCurrencyCode();
            if (item.isHasResult()) {
                amount[0] = amount[0].add(item.getAmount());
            }
        });

        result[0] = amount[0];
        result[1] = currency[0];
        return result;
    }

    /**
     * 根据传入的运单号查询计费重
     *
     * @param orderNo orderNo
     * @return QuotationCalcResult
     */
    @Override
    public QuotationCalcResult calcWaybillFeeWeight(String orderNo) {

        if (StringUtils.isEmpty(orderNo)) {
            return null;
        }
        OrdOrder order = orderService.findByOrderNo(orderNo);
        if (order == null) {
            return null;
        }

        List<OrdSubOrder> subOrders = subOrderService.findByOrderNo(orderNo);
        //计算体积
        BigDecimal volume = BigDecimal.ZERO;
        for (OrdSubOrder subOrder : subOrders) {
            if (subOrder.getWidth() != null && subOrder.getHeight() != null && subOrder.getLength() != null) {
                try {
                    volume = getSubOrderVolume(volume, subOrder);
                } catch (Exception e) {
                    return null;
                }
            }
        }

        ProductDto product = productService.findByUidAndTime(order.getProductUid(), order.getReceiveCargoTime());
        if (product == null) {
            return null;
        }

        QuotationCalcResult result = new QuotationCalcResult();
        if (product.getIsVolume() && volume.compareTo(BigDecimal.ZERO) > 0
                && product.getVolumeFactor().compareTo(BigDecimal.ZERO) > 0) {
            BigDecimal targetWeight;
            try {
                targetWeight = unitConversionService.getTargetWeight(order.getSupWeight(),
                        order.getSupWeightUnit(), KG);
            } catch (Exception e) {
                return null;
            }
            BigDecimal volumeWeight = volume.divide(product.getVolumeFactor(), 3, RoundingMode.HALF_UP);
            result.setVolumeWeight(volumeWeight);
            result.setVolumeWeightUnitCode(KG);
            if (MAX.equals(product.getWeightValueTye())) {
                result.setFeeWeight(volumeWeight.compareTo(targetWeight) > 0 ? volumeWeight : targetWeight);
            } else {
                result.setFeeWeight(volumeWeight.compareTo(targetWeight) > 0 ? targetWeight : volumeWeight);
            }
            result.setFeeWeightUnitCode(KG);
        } else {
            result.setFeeWeight(order.getSupWeight());
            result.setFeeWeightUnitCode(order.getSupWeightUnit());
        }

        return result;
    }

    private BigDecimal getSubOrderVolume(BigDecimal volume, OrdSubOrder subOrder) {
        BigDecimal width =
                unitConversionService.getTargetLength(subOrder.getWidth(), subOrder.getLwhUnit(), "cm");
        BigDecimal height =
                unitConversionService.getTargetLength(subOrder.getHeight(), subOrder.getLwhUnit(), "cm");
        BigDecimal length =
                unitConversionService.getTargetLength(subOrder.getLength(), subOrder.getLwhUnit(), "cm");
        BigDecimal singleVolume = length.multiply(width).multiply(height);
        volume = volume.add(singleVolume);
        return volume;
    }
}
